Add real snippet saving and fix snippet evaluation in client apps

This commit is contained in:
Andrew Kingston 2024-03-06 19:07:16 +00:00
parent 01679fbd01
commit 4d271ccb53
3 changed files with 55 additions and 60 deletions

View File

@ -6,6 +6,7 @@
Icon,
AbsTooltip,
TooltipType,
notifications,
} from "@budibase/bbui"
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
import { decodeJSBinding, encodeJSBinding } from "@budibase/string-templates"
@ -22,24 +23,38 @@
let drawer
let name = ""
let code = ""
let loading = false
$: key = snippet?.name
$: name = snippet?.name || "MySnippet"
$: code = snippet?.code ? encodeJSBinding(snippet.code) : ""
$: rawJS = decodeJSBinding(code)
$: nameError = validateName(name)
$: nameError = validateName(name, $snippetStore)
const saveSnippet = async () => {
await snippetStore.saveSnippet({
name,
code: rawJS,
})
drawer.hide()
loading = true
try {
await snippetStore.saveSnippet({
name,
code: rawJS,
})
drawer.hide()
notifications.success(`Snippet ${name} saved`)
} catch (error) {
notifications.error("Error saving snippet")
}
loading = false
}
const deleteSnippet = async () => {
await snippetStore.deleteSnippet(snippet.name)
drawer.hide()
loading = true
try {
await snippetStore.deleteSnippet(snippet.name)
drawer.hide()
} catch (error) {
notifications.error("Error deleting snippet")
}
loading = false
}
// Validating function names is not as easy as you think. A simple regex does
@ -48,7 +63,7 @@
// Instead, we can run a simple regex to roughly validate it, then basically
// try executing it and see if it's valid JS. The initial regex prevents
// against any potential XSS attacks here.
const validateName = name => {
const validateName = (name, snippets) => {
if (!name?.length) {
return "Name is required"
}
@ -58,6 +73,9 @@
if (!roughValidNameRegex.test(name)) {
return "No special characters or spaces"
}
if (snippets.some(snippet => snippet.name === name)) {
return "That name is already in use"
}
const js = `(function ${name}(){return true})()`
try {
return eval(js) === true ? null : "Invalid name"
@ -89,9 +107,11 @@
</svelte:fragment>
<svelte:fragment slot="buttons">
{#if snippet}
<Button warning on:click={deleteSnippet}>Delete</Button>
<Button warning on:click={deleteSnippet} disabled={loading}>
Delete
</Button>
{/if}
<Button cta on:click={saveSnippet} disabled={nameError != null}>
<Button cta on:click={saveSnippet} disabled={loading || nameError != null}>
Save
</Button>
</svelte:fragment>

View File

@ -1,58 +1,33 @@
import { writable } from "svelte/store"
const EXAMPLE_SNIPPETS = [
{
name: "Square",
code: `
return function(num) {
return num * num
}
`,
},
{
name: "HelloWorld",
code: `
return "Hello, world!"
`,
},
{
name: "Colorful",
code: `
let a = null
let b = "asdasd"
let c = 123123
let d = undefined
let e = [1, 2, 3]
let f = { foo: "bar" }
let g = Math.round(1.234)
if (a === b) {
return c ?? e
}
return d || f
// comment
let h = 1 + 2 + 3 * 3
let i = true
let j = false
`,
},
]
import { writable, get } from "svelte/store"
import { API } from "api"
import { appStore } from "./app"
const createSnippetStore = () => {
const store = writable(EXAMPLE_SNIPPETS)
const store = writable([])
const syncMetadata = metadata => {
store.set(metadata?.snippets || EXAMPLE_SNIPPETS)
store.set(metadata?.snippets || [])
}
const saveSnippet = updatedSnippet => {
store.update(state => [
...state.filter(snippet => snippet.name !== updatedSnippet.name),
const saveSnippet = async updatedSnippet => {
const snippets = [
...get(store).filter(snippet => snippet.name !== updatedSnippet.name),
updatedSnippet,
])
]
const app = await API.saveAppMetadata({
appId: get(appStore).appId,
metadata: { snippets },
})
syncMetadata(app)
}
const deleteSnippet = snippetName => {
store.update(state => state.filter(snippet => snippet.name !== snippetName))
const deleteSnippet = async snippetName => {
const snippets = get(store).filter(snippet => snippet.name !== snippetName)
const app = await API.saveAppMetadata({
appId: get(appStore).appId,
metadata: { snippets },
})
syncMetadata(app)
}
return {

View File

@ -1,6 +1,6 @@
import { derived } from "svelte/store"
import { appStore } from "../app.js"
export const snippets = derived(appStore, $appStore => $appStore.snippets)
snippets.subscribe(console.log)
export const snippets = derived(appStore, $appStore => {
return $appStore?.application?.snippets || []
})