Improve snippet drawer

This commit is contained in:
Andrew Kingston 2024-03-06 14:33:17 +00:00
parent d38a6ed0d3
commit 7dc67185ed
2 changed files with 95 additions and 26 deletions

View File

@ -7,7 +7,7 @@
Body,
Button,
} from "@budibase/bbui"
import { createEventDispatcher, getContext, onMount } from "svelte"
import { createEventDispatcher, onMount } from "svelte"
import {
isValid,
decodeJSBinding,
@ -44,6 +44,7 @@
export let snippets = null
export let autofocusEditor = false
export let placeholder = null
export let showTabBar = true
const Modes = {
Text: "Text",
@ -234,10 +235,8 @@
<DrawerContent padding={false}>
<div class="binding-panel">
<div class="main">
{#if showTabBar}
<div class="tabs">
{#if $$slots.tabs}
<slot name="tabs" />
{:else}
<div class="editor-tabs">
{#each editorModeOptions as editorMode}
<ActionButton
@ -250,7 +249,6 @@
</ActionButton>
{/each}
</div>
{/if}
<div class="side-tabs">
{#each sidePanelOptions as panel}
<ActionButton
@ -264,6 +262,7 @@
{/each}
</div>
</div>
{/if}
<div class="editor">
{#if mode === Modes.Text}
<CodeEditor

View File

@ -1,25 +1,36 @@
<script>
import { Button, Drawer, Input } from "@budibase/bbui"
import {
Button,
Drawer,
Input,
Icon,
AbsTooltip,
TooltipType,
} from "@budibase/bbui"
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
import { decodeJSBinding, encodeJSBinding } from "@budibase/string-templates"
import { snippetStore } from "stores/builder"
export let snippet
let drawer
let code = ""
export const show = () => drawer.show()
export const hide = () => drawer.hide()
const roughValidNameRegex = /^[_$A-Z\xA0-\uFFFF][_$A-Z0-9\xA0-\uFFFF]*$/i
let drawer
let name = ""
let code = ""
$: key = snippet?.name
$: name = snippet?.name || "MySnippet"
$: code = snippet?.code ? encodeJSBinding(snippet.code) : ""
$: rawJS = decodeJSBinding(code)
$: nameValid = validateName(name)
const saveSnippet = async () => {
await snippetStore.saveSnippet({
name:
snippet?.name || "Snippet_" + Math.random().toString().substring(2, 5),
name,
code: rawJS,
})
drawer.hide()
@ -29,14 +40,56 @@
await snippetStore.deleteSnippet(snippet.name)
drawer.hide()
}
// Validating function names is not as easy as you think. A simple regex does
// not work, as there are a bunch of reserved words. The correct regex for
// this is about 12K characters long.
// 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 => {
if (!roughValidNameRegex.test(name)) {
return false
}
const js = `(function ${name}(){return true})()`
try {
return eval(js) === true
} catch (error) {
return false
}
}
</script>
<Drawer title={snippet ? "Edit snippet" : "Create snippet"} bind:this={drawer}>
<Drawer bind:this={drawer}>
<svelte:fragment slot="title">
{#if snippet}
{snippet.name}
{:else}
<div class="name" class:invalid={!nameValid}>
<span>Name</span>
<Input bind:value={name} />
{#if !nameValid}
<AbsTooltip
text="Alphanumeric characters only, with no spaces"
type={TooltipType.Negative}
>
<Icon
name="Help"
size="S"
color="var(--spectrum-global-color-red-400)"
/>
</AbsTooltip>
{/if}
</div>
{/if}
</svelte:fragment>
<svelte:fragment slot="buttons">
{#if snippet}
<Button warning on:click={deleteSnippet}>Delete</Button>
{/if}
<Button cta on:click={saveSnippet} disabled={!rawJS?.length}>Save</Button>
<Button cta on:click={saveSnippet} disabled={!rawJS?.length || !nameValid}>
Save
</Button>
</svelte:fragment>
<svelte:fragment slot="body">
{#key key}
@ -44,6 +97,7 @@
allowHBS={false}
allowJS
allowSnippets={false}
showTabBar={false}
placeholder="return function(input) &#10100; ... &#10101;"
value={code}
on:change={e => (code = e.detail)}
@ -55,3 +109,19 @@
{/key}
</svelte:fragment>
</Drawer>
<style>
.name {
display: flex;
gap: var(--spacing-l);
align-items: center;
position: relative;
}
.name.invalid :global(input) {
width: 200px;
}
.name :global(.icon) {
position: absolute;
right: 10px;
}
</style>