From 7dc67185ed31d46d0521bcf9b0ea50a446508d11 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 6 Mar 2024 14:33:17 +0000 Subject: [PATCH] Improve snippet drawer --- .../common/bindings/BindingPanel.svelte | 35 ++++---- .../common/bindings/SnippetDrawer.svelte | 86 +++++++++++++++++-- 2 files changed, 95 insertions(+), 26 deletions(-) diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index bba3832efd..f03c18fde3 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -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 @@
-
- {#if $$slots.tabs} - - {:else} + {#if showTabBar} +
{#each editorModeOptions as editorMode} {/each}
- {/if} -
- {#each sidePanelOptions as panel} - changeSidePanel(panel)} - > - - - {/each} +
+ {#each sidePanelOptions as panel} + changeSidePanel(panel)} + > + + + {/each} +
-
+ {/if}
{#if mode === Modes.Text} - 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 + } + } - + + + {#if snippet} + {snippet.name} + {:else} +
+ Name + + {#if !nameValid} + + + + {/if} +
+ {/if} +
{#if snippet} {/if} - + {#key key} @@ -44,6 +97,7 @@ allowHBS={false} allowJS allowSnippets={false} + showTabBar={false} placeholder="return function(input) ❴ ... ❵" value={code} on:change={e => (code = e.detail)} @@ -55,3 +109,19 @@ {/key}
+ +