Skip to content

Commit b06af4e

Browse files
authored
Merge pull request #16756 from Budibase/fix/email-template-bindings
Fix email template bindings
2 parents cd62ddc + b595a98 commit b06af4e

File tree

5 files changed

+115
-37
lines changed

5 files changed

+115
-37
lines changed

packages/backend-core/src/Endpoint/EndpointGroupList.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,18 @@ export default class EndpointGroupList {
2525
// sort endpoints with a URL parameters after the static endpoints -
2626
// for example, endpoints /api/queries/:queryId and /api/queries/accessible
2727
// can overlap, if the parameter comes before the accessible it'll be unreachable
28-
endpoints.sort((a, b) => {
29-
const aHasColon = a.url.includes(":")
30-
const bHasColon = b.url.includes(":")
28+
// maintain original order within each group (static vs parameterized)
29+
const staticEndpoints = []
30+
const parameterizedEndpoints = []
3131

32-
if (aHasColon && !bHasColon) return 1
33-
if (!aHasColon && bHasColon) return -1
34-
return a.url.localeCompare(b.url)
35-
})
36-
return endpoints
32+
for (const endpoint of endpoints) {
33+
if (endpoint.url.includes(":")) {
34+
parameterizedEndpoints.push(endpoint)
35+
} else {
36+
staticEndpoints.push(endpoint)
37+
}
38+
}
39+
40+
return [...staticEndpoints, ...parameterizedEndpoints]
3741
}
3842
}

packages/builder/src/components/integration/QueryEditor.svelte

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
LIGHT: "default",
1313
}
1414
15-
export let label
15+
export let label = undefined
1616
export let value = ""
1717
export let readOnly = false
1818
export let lineNumbers = true
@@ -58,6 +58,13 @@
5858
editor.focus()
5959
}
6060
61+
export function insertAtCursor(text) {
62+
if (editor) {
63+
editor.replaceSelection(text)
64+
editor.focus()
65+
}
66+
}
67+
6168
const modes = {
6269
js: {
6370
name: "javascript",

packages/builder/src/components/portal/page/Breadcrumb.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { Icon } from "@budibase/bbui"
33
44
export let url: string | undefined = undefined
5-
export let text: string
5+
export let text: string | undefined = undefined
66
</script>
77

88
<div>

packages/builder/src/pages/builder/portal/settings/email/[template].svelte

Lines changed: 91 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<script>
1+
<script lang="ts">
22
import { onMount, tick } from "svelte"
33
import {
44
Button,
@@ -14,25 +14,64 @@
1414
import Editor from "@/components/integration/QueryEditor.svelte"
1515
import TemplateBindings from "./_components/TemplateBindings.svelte"
1616
import { Breadcrumbs, Breadcrumb } from "@/components/portal/page"
17+
import type { Template, GlobalTemplateBinding } from "@budibase/types"
18+
19+
// QueryEditor component interface based on exposed methods
20+
interface QueryEditor {
21+
// eslint-disable-next-line no-unused-vars
22+
set: (newValue: string, opts?: string) => Promise<void>
23+
update: (_: string) => void
24+
resize: () => void
25+
focus: () => void
26+
insertAtCursor: (_: string) => void
27+
}
28+
29+
// Tab select event interface
30+
interface TabSelectEvent {
31+
detail: string
32+
}
33+
34+
// Binding tab select event handler
35+
function handleBindingTabSelect(event: TabSelectEvent): void {
36+
selectedBindingTab = event.detail
37+
}
38+
39+
// Editor change event interface
40+
interface EditorChangeEvent {
41+
detail: {
42+
value: string
43+
}
44+
}
1745
1846
// this is the email purpose
19-
export let template
47+
export let template: string
2048
21-
let htmlEditor
22-
let mounted = false
49+
let htmlEditor: QueryEditor | null = null
50+
let mounted: boolean = false
51+
let selectedBindingTab: string = ""
2352
2453
$: selectedTemplate = $email.templates?.find(
25-
({ purpose }) => purpose === template
26-
)
27-
$: name = $email.definitions?.info[template]?.name
28-
$: description = $email.definitions?.info[template]?.description
29-
$: baseTemplate = $email.templates?.find(({ purpose }) => purpose === "base")
30-
$: templateBindings =
31-
$email.definitions?.bindings?.[selectedTemplate?.purpose] || []
54+
({ purpose }: Template) => purpose === template
55+
) as Template | undefined
56+
$: name = $email.definitions?.info[template]?.name as string | undefined
57+
$: description = $email.definitions?.info[template]?.description as
58+
| string
59+
| undefined
60+
$: baseTemplate = $email.templates?.find(
61+
({ purpose }: Template) => purpose === "base"
62+
) as Template | undefined
63+
$: templateBindings = selectedTemplate?.purpose
64+
? (($email.definitions?.bindings?.[selectedTemplate.purpose] ||
65+
[]) as GlobalTemplateBinding[])
66+
: []
3267
$: previewContent = makePreviewContent(baseTemplate, selectedTemplate)
3368
34-
async function saveTemplate() {
69+
async function saveTemplate(): Promise<void> {
3570
try {
71+
if (!selectedTemplate) {
72+
notifications.error("No template selected to save")
73+
return
74+
}
3675
// Save your template config
3776
await email.saveTemplate(selectedTemplate)
3877
notifications.success("Template saved")
@@ -41,33 +80,61 @@
4180
}
4281
}
4382
44-
function setTemplateBinding(binding) {
45-
htmlEditor.update((selectedTemplate.contents += `{{ ${binding.name} }}`))
83+
function setTemplateBinding(binding: GlobalTemplateBinding): void {
84+
if (!selectedTemplate) {
85+
console.warn("No template selected")
86+
return
87+
}
88+
if (!htmlEditor) {
89+
console.warn("Editor not available")
90+
return
91+
}
92+
93+
// Insert the binding at the current cursor position
94+
const bindingText = `{{ ${binding.name} }}`
95+
htmlEditor.insertAtCursor(bindingText)
4696
}
4797
48-
const makePreviewContent = (baseTemplate, selectedTemplate) => {
98+
function makePreviewContent(
99+
baseTemplate: Template | undefined,
100+
selectedTemplate: Template | undefined
101+
): string {
49102
if (!selectedTemplate) {
50103
return ""
51104
}
52105
if (selectedTemplate.purpose === "base") {
53106
return selectedTemplate.contents
54107
}
55-
const base = baseTemplate?.contents ?? ""
108+
const base: string = baseTemplate?.contents ?? ""
56109
return base.replace("{{ body }}", selectedTemplate?.contents ?? "")
57110
}
58111
112+
// Set initial binding tab based on available bindings, only if not already set
113+
$: {
114+
if (!selectedBindingTab && templateBindings) {
115+
selectedBindingTab = templateBindings.length ? "Template" : "Common"
116+
}
117+
}
118+
59119
onMount(() => {
60120
mounted = true
61121
})
62122
63-
async function fixMountBug({ detail }) {
123+
async function fixMountBug(event: TabSelectEvent): Promise<void> {
124+
const { detail } = event
64125
if (detail === "Edit") {
65126
await tick()
66127
mounted = true
67128
} else {
68129
mounted = false
69130
}
70131
}
132+
133+
function handleEditorChange(event: EditorChangeEvent): void {
134+
if (selectedTemplate) {
135+
selectedTemplate.contents = event.detail.value
136+
}
137+
}
71138
</script>
72139

73140
<Layout gap="L" noPadding>
@@ -95,27 +162,27 @@
95162
editorHeight={640}
96163
bind:this={htmlEditor}
97164
mode="handlebars"
98-
on:change={e => {
99-
selectedTemplate.contents = e.detail.value
100-
}}
165+
on:change={handleEditorChange}
101166
value={selectedTemplate?.contents}
102167
/>
103168
</div>
104169
<div class="bindings-editor">
105170
<Heading size="XS">Bindings</Heading>
106171
{#if mounted}
107-
<Tabs noHorizPadding selected="Template">
172+
<Tabs
173+
noHorizPadding
174+
selected={selectedBindingTab}
175+
on:select={handleBindingTabSelect}
176+
>
108177
<Tab title="Template">
109178
<TemplateBindings
110-
title="Template Bindings"
111179
bindings={templateBindings}
112180
onBindingClick={setTemplateBinding}
113181
/>
114182
</Tab>
115183
<Tab title="Common">
116184
<TemplateBindings
117-
title="Common Bindings"
118-
bindings={$email?.definitions?.bindings?.common}
185+
bindings={$email?.definitions?.bindings?.common || []}
119186
onBindingClick={setTemplateBinding}
120187
/>
121188
</Tab>

packages/builder/src/pages/builder/portal/settings/email/_components/TemplateBindings.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
<script>
1+
<script lang="ts">
22
import { Body, Detail } from "@budibase/bbui"
33
4-
export let bindings
5-
export let onBindingClick = () => {}
4+
export let bindings: any[]
5+
export let onBindingClick: Function = () => {}
66
</script>
77

88
<!-- svelte-ignore a11y-no-static-element-interactions -->

0 commit comments

Comments
 (0)