diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index 7b5a7612a9..49f8cddccd 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -22,7 +22,7 @@ services: JWT_SECRET: ${JWT_SECRET} LOG_LEVEL: info SENTRY_DSN: https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131 - ENABLE_ANALYTICS: true + ENABLE_ANALYTICS: "true" depends_on: - worker-service diff --git a/hosting/envoy.dev.yaml b/hosting/envoy.dev.yaml index e12bc2c0e0..2ba08fe595 100644 --- a/hosting/envoy.dev.yaml +++ b/hosting/envoy.dev.yaml @@ -26,6 +26,23 @@ static_resources: cluster: redis-service prefix_rewrite: "/" + - match: { prefix: "/api/" } + route: + cluster: server-dev + + - match: { prefix: "/app_" } + route: + cluster: server-dev + + - match: { prefix: "/builder/" } + route: + cluster: builder-dev + + - match: { prefix: "/builder" } + route: + cluster: builder-dev + prefix_rewrite: "/builder/" + # minio is on the default route because this works # best, minio + AWS SDK doesn't handle path proxy - match: { prefix: "/" } @@ -77,3 +94,32 @@ static_resources: socket_address: address: redis-service port_value: 6379 + + - name: server-dev + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: server-dev + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 172.17.0.1 + port_value: 4001 + + - name: builder-dev + connect_timeout: 15s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: builder-dev + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 172.17.0.1 + port_value: 3000 + diff --git a/package.json b/package.json index 037e9c4b1c..42010f760a 100644 --- a/package.json +++ b/package.json @@ -17,16 +17,17 @@ "svelte": "^3.30.0" }, "scripts": { - "bootstrap": "lerna bootstrap", + "bootstrap": "lerna link && lerna bootstrap", "build": "lerna run build", "initialise": "lerna run initialise", "publishdev": "lerna run publishdev", "publishnpm": "yarn build && lerna publish --force-publish", "restore": "npm run clean && npm run bootstrap && npm run build", - "nuke": "rimraf ~/.budibase && npm run restore", + "nuke": "rimraf ~/.budibase && npm run restore && lerna run --parallel dev:stack:nuke", "clean": "lerna clean", "kill-port": "kill-port 4001", - "dev": "yarn run kill-port && node ./scripts/symlinkDev.js && lerna run --parallel dev:builder --concurrency 1", + "dev": "yarn run kill-port && lerna link && lerna run --parallel dev:builder --concurrency 1", + "dev:noserver": "lerna link && lerna run --parallel dev:builder --concurrency 1 --ignore @budibase/server", "test": "lerna run test", "lint": "eslint packages", "lint:fix": "eslint --fix packages", diff --git a/packages/bbui/.gitignore b/packages/bbui/.gitignore new file mode 100644 index 0000000000..f0a5039c33 --- /dev/null +++ b/packages/bbui/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +node_modules +/dist/ +/public/svench/ +.idea \ No newline at end of file diff --git a/packages/bbui/README.md b/packages/bbui/README.md new file mode 100644 index 0000000000..9b71a86cab --- /dev/null +++ b/packages/bbui/README.md @@ -0,0 +1,42 @@ +# Budibase bbui + +A package that handles all common components across the Budibase organisation. You can find the current live version [Here](http://bbui.budibase.com). + +## Install + +1. Clone +2. `npm install` +3. `npm run svench` + +(Note: yarn won't work!) + +## Example workflow to create a component + +1. Create a file: `Headline.svelte` +2. Create a Svench file: `Headline.svench` +3. Build component and add variants to the Svench file. +4. Once done, re-export the file in `src/index.js`. +5. Publish, update the package in the main project and profit. + +## Guidelines +### Making components + +1. Think about re-usability +2. Use the css custom properties (variables) that are in the css stylesheet. This makes it easy to tweak things later down the line. +3. Opt to forward events (` +``` \ No newline at end of file diff --git a/packages/bbui/src/Actions/PositionDropdown.svench.svx b/packages/bbui/src/Actions/PositionDropdown.svench.svx new file mode 100644 index 0000000000..695232535c --- /dev/null +++ b/packages/bbui/src/Actions/PositionDropdown.svench.svx @@ -0,0 +1,81 @@ + + +### Position Dropdown Action + +This action positions an element close to it's anchor, either above or below it, depending on the amount of space that exists. There's also an option to align the dropdown to the right instead of the left of the anchor. An example of how to use it follows: + +```html + + + + +{#if visible} + +
+ Some content here. +
+
+{/if} +``` + +Here are some components that currently use this action: + + + + {#each options as option} + + {/each} + + + + +
+ +
+ alert('Closed!')} + bind:this={dropdownLeft} + width="175px" + borderColor="#d1d1d1ff" + anchor={anchorLeft} + align="left"> + + +
diff --git a/packages/bbui/src/Actions/autoresize_textarea.js b/packages/bbui/src/Actions/autoresize_textarea.js new file mode 100644 index 0000000000..227519dd20 --- /dev/null +++ b/packages/bbui/src/Actions/autoresize_textarea.js @@ -0,0 +1,14 @@ +function resize({ target }) { + target.style.height = "1px" + target.style.height = +target.scrollHeight + "px" +} + +export default function text_area_resize(el) { + resize({ target: el }) + el.style.overflow = "hidden" + el.addEventListener("input", resize) + + return { + destroy: () => el.removeEventListener("input", resize), + } +} diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js new file mode 100644 index 0000000000..9257af5f5b --- /dev/null +++ b/packages/bbui/src/Actions/click_outside.js @@ -0,0 +1,18 @@ +export default function clickOutside(element, callbackFunction) { + function onClick(event) { + if (!element.contains(event.target)) { + callbackFunction() + } + } + + document.body.addEventListener("click", onClick, true) + + return { + update(newCallbackFunction) { + callbackFunction = newCallbackFunction + }, + destroy() { + document.body.removeEventListener("click", onClick, true) + }, + } +} diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js new file mode 100644 index 0000000000..01d0ec4553 --- /dev/null +++ b/packages/bbui/src/Actions/position_dropdown.js @@ -0,0 +1,66 @@ +export default function positionDropdown(element, { anchor, align }) { + let positionSide = "top" + let maxHeight = 0 + let dimensions = getDimensions(anchor) + + function getDimensions() { + const { + bottom, + top: spaceAbove, + left, + width, + } = anchor.getBoundingClientRect() + const spaceBelow = window.innerHeight - bottom + const containerRect = element.getBoundingClientRect() + + let y + + if (spaceAbove > spaceBelow) { + positionSide = "bottom" + maxHeight = spaceAbove - 20 + y = window.innerHeight - spaceAbove + } else { + positionSide = "top" + y = bottom + maxHeight = spaceBelow - 20 + } + + return { + [positionSide]: y, + left, + width, + containerWidth: containerRect.width, + } + } + + function calcLeftPosition() { + return align === "right" + ? dimensions.left + dimensions.width - dimensions.containerWidth + : dimensions.left + } + + element.style.position = "absolute" + element.style.zIndex = "9999" + element.style.minWidth = `${dimensions.width}px` + element.style.maxHeight = `${maxHeight.toFixed(0)}px` + element.style.transformOrigin = `center ${positionSide}` + element.style[positionSide] = `${dimensions[positionSide]}px` + element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px` + + const resizeObserver = new ResizeObserver(entries => { + for (let entry of entries) { + dimensions = getDimensions() + element.style[positionSide] = `${dimensions[positionSide]}px` + element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px` + } + }) + + resizeObserver.observe(anchor) + resizeObserver.observe(element) + + return { + destroy() { + resizeObserver.disconnect() + }, + } +} diff --git a/packages/bbui/src/Button/Button.svelte b/packages/bbui/src/Button/Button.svelte new file mode 100644 index 0000000000..5e403287e1 --- /dev/null +++ b/packages/bbui/src/Button/Button.svelte @@ -0,0 +1,212 @@ + + +{#if href} + + + +{:else} + +{/if} + + diff --git a/packages/bbui/src/Button/Button.svench b/packages/bbui/src/Button/Button.svench new file mode 100644 index 0000000000..9149f76944 --- /dev/null +++ b/packages/bbui/src/Button/Button.svench @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+ + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/bbui/src/Button/Close.svelte b/packages/bbui/src/Button/Close.svelte new file mode 100644 index 0000000000..f2b76cdb38 --- /dev/null +++ b/packages/bbui/src/Button/Close.svelte @@ -0,0 +1,59 @@ + + + + + diff --git a/packages/bbui/src/Button/Close.svench b/packages/bbui/src/Button/Close.svench new file mode 100644 index 0000000000..5bb7542b11 --- /dev/null +++ b/packages/bbui/src/Button/Close.svench @@ -0,0 +1,36 @@ + + + + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
diff --git a/packages/bbui/src/Button/TextButton.svelte b/packages/bbui/src/Button/TextButton.svelte new file mode 100644 index 0000000000..351a2c45bf --- /dev/null +++ b/packages/bbui/src/Button/TextButton.svelte @@ -0,0 +1,128 @@ + + +{#if href} + +{:else} + +{/if} + + diff --git a/packages/bbui/src/Button/TextButton.svench b/packages/bbui/src/Button/TextButton.svench new file mode 100644 index 0000000000..e3c3477805 --- /dev/null +++ b/packages/bbui/src/Button/TextButton.svench @@ -0,0 +1,69 @@ + + + + + +
+ alert('Clicked!')}> + + Add View + + alert('Clicked!')}> + + Add Column + + alert('Clicked!')}> + + Add Row + + alert('Clicked!')}> + + Disabled Text Button + + alert('Clicked!')}> + + Active Calculation + +
+
+ + +
+ alert('Clicked!')}> + + Add View + + alert('Clicked!')}> + + Add Column + + alert('Clicked!')}> + + Add Row + + alert('Clicked!')}> + + Delete + + alert('Clicked!')}> + + Calculate + +
+
+ + +
+ This is a link +
+
diff --git a/packages/bbui/src/DatePicker/DatePicker.svelte b/packages/bbui/src/DatePicker/DatePicker.svelte new file mode 100644 index 0000000000..2f87a48a43 --- /dev/null +++ b/packages/bbui/src/DatePicker/DatePicker.svelte @@ -0,0 +1,45 @@ + + +
+ {#if label} + + {/if} + +
+ + diff --git a/packages/bbui/src/DatePicker/DatePicker.svench b/packages/bbui/src/DatePicker/DatePicker.svench new file mode 100644 index 0000000000..ed0d480a6f --- /dev/null +++ b/packages/bbui/src/DatePicker/DatePicker.svench @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/packages/bbui/src/Drawer/Drawer.svelte b/packages/bbui/src/Drawer/Drawer.svelte new file mode 100644 index 0000000000..2f0cc1b4f3 --- /dev/null +++ b/packages/bbui/src/Drawer/Drawer.svelte @@ -0,0 +1,88 @@ + + + + +{#if visible} + +
+
+
+
{title}
+ +
+
+ + +
+
+ +
+
+{/if} + + diff --git a/packages/bbui/src/Drawer/Drawer.svench b/packages/bbui/src/Drawer/Drawer.svench new file mode 100644 index 0000000000..9d652e06ff --- /dev/null +++ b/packages/bbui/src/Drawer/Drawer.svench @@ -0,0 +1,42 @@ + + + + + + + alert('You closed the drawer!')}> +

+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Placeat, + architecto assumenda! Quia harum hic numquam, soluta maiores facere + explicabo vero obcaecati voluptas, qui placeat ad, dolorem recusandae + labore quos? Nisi! +

+
+
+ + + alert('You closed the drawer!')}> + + + +
This describes the drawer!
+
Some content here
+
+
diff --git a/packages/bbui/src/DropdownMenu/DropdownMenu.svelte b/packages/bbui/src/DropdownMenu/DropdownMenu.svelte new file mode 100644 index 0000000000..decc5bb4e2 --- /dev/null +++ b/packages/bbui/src/DropdownMenu/DropdownMenu.svelte @@ -0,0 +1,77 @@ + + +{#if open} + + + +{/if} + + diff --git a/packages/bbui/src/DropdownMenu/DropdownMenu.svench b/packages/bbui/src/DropdownMenu/DropdownMenu.svench new file mode 100644 index 0000000000..af4e32db2e --- /dev/null +++ b/packages/bbui/src/DropdownMenu/DropdownMenu.svench @@ -0,0 +1,161 @@ + + + + + +
+ +
+ +
    +
  • Item 1
  • +
  • Item 2
  • +
  • Item 3
  • +
+
+
+ + +
+ +
+ +
    +
  • Item 1
  • +
  • Item 2
  • +
  • Item 3
  • +
+
+
+ + +
+ +
+ +
    +
  • + + Edit +
  • +
  • + + Delete +
  • +
  • + + Sort A - Z +
  • +
  • + + Sort Z - A +
  • +
+
+
+ +
+ +
+ +
    +
  • + + Edit +
  • +
  • + + Delete +
  • +
  • + + Sort A - Z +
  • +
  • + + Sort Z - A +
  • +
+
+
+ +
+ +
+ alert('Closed!')} + bind:this={dropdownLeft} + width="175px" + borderColor="#d1d1d1ff" + anchor={anchorLeft} + align="left"> +
    +
  • + + Edit +
  • +
  • + + Delete +
  • +
  • + + Sort A - Z +
  • +
  • + + Sort Z - A +
  • +
+
+
\ No newline at end of file diff --git a/packages/bbui/src/Dropzone/Dropzone.svelte b/packages/bbui/src/Dropzone/Dropzone.svelte new file mode 100644 index 0000000000..4f4f4bc7bf --- /dev/null +++ b/packages/bbui/src/Dropzone/Dropzone.svelte @@ -0,0 +1,295 @@ + + +
+ {#if selectedImage} +
    +
  • +
    +
    + + {selectedImage.name} +
    +

    + {#if selectedImage.size <= BYTES_IN_MB} + {selectedImage.size / BYTES_IN_KB}KB + {:else}{selectedImage.size / BYTES_IN_MB}MB{/if} +

    +
    +
    + +
    + {#if selectedImageIdx !== 0} + + {/if} + preview + {#if selectedImageIdx !== files.length - 1} + + {/if} +
  • +
+ {/if} + + + +

Drop your files here

+ +
+ + diff --git a/packages/bbui/src/Dropzone/Dropzone.svench b/packages/bbui/src/Dropzone/Dropzone.svench new file mode 100644 index 0000000000..110195ab5a --- /dev/null +++ b/packages/bbui/src/Dropzone/Dropzone.svench @@ -0,0 +1,17 @@ + + + + + diff --git a/packages/bbui/src/Dropzone/fileTypes.js b/packages/bbui/src/Dropzone/fileTypes.js new file mode 100644 index 0000000000..1ebd85070b --- /dev/null +++ b/packages/bbui/src/Dropzone/fileTypes.js @@ -0,0 +1,5 @@ +export const FILE_TYPES = { + IMAGE: ["png", "tiff", "gif", "raw", "jpg", "jpeg", "svg"], + CODE: ["js", "rs", "py", "java", "rb", "hs", "yml"], + DOCUMENT: ["odf", "docx", "doc", "pdf", "csv"], +} diff --git a/packages/bbui/src/Form/Checkbox.svelte b/packages/bbui/src/Form/Checkbox.svelte new file mode 100644 index 0000000000..21d5450123 --- /dev/null +++ b/packages/bbui/src/Form/Checkbox.svelte @@ -0,0 +1,140 @@ + + +
+ +
+
+
+
+
+ +
+ + diff --git a/packages/bbui/src/Form/Checkbox.svench.svx b/packages/bbui/src/Form/Checkbox.svench.svx new file mode 100644 index 0000000000..b3246ec9d2 --- /dev/null +++ b/packages/bbui/src/Form/Checkbox.svench.svx @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + +## Multiple checkboxes +Use an array and an each block to use multiple checkboxes +```svelte + + +{#each menu as {text, checked}} + + + +{/each} +``` + + +
+ {#each menu as {text, checked}} + + + + {/each} +
+
+ + \ No newline at end of file diff --git a/packages/bbui/src/Form/DataList.svelte b/packages/bbui/src/Form/DataList.svelte new file mode 100644 index 0000000000..6f5855d86a --- /dev/null +++ b/packages/bbui/src/Form/DataList.svelte @@ -0,0 +1,158 @@ + + +{#if label} + +{/if} +
+ + + { + updateValue(e) + handleBlur(e) + }} + value={value || ''} + type="text" /> +
+ +
+
+ + diff --git a/packages/bbui/src/Form/DataList.svench b/packages/bbui/src/Form/DataList.svench new file mode 100644 index 0000000000..eea8294dea --- /dev/null +++ b/packages/bbui/src/Form/DataList.svench @@ -0,0 +1,57 @@ + + + + + {#each options as option} + + {/each} + + + + + + {#each options as option} + + {/each} + + + + + + {#each options as option} + + {/each} + + + + + + {#each options as option} + + {/each} + + + + + + {#each options as option} + + {/each} + + + + + + {#each options as option} + + {/each} + + + diff --git a/packages/bbui/src/Form/Input.svelte b/packages/bbui/src/Form/Input.svelte new file mode 100644 index 0000000000..e8afcffd36 --- /dev/null +++ b/packages/bbui/src/Form/Input.svelte @@ -0,0 +1,190 @@ + + +
+ {#if label || edit} +
+ {#if label} + + {/if} + {#if edit} +
+ + +
+ {/if} +
+ {/if} + + {#if error} +
{error}
+ {/if} +
+ + diff --git a/packages/bbui/src/Form/Input.svench b/packages/bbui/src/Form/Input.svench new file mode 100644 index 0000000000..1b95727a1a --- /dev/null +++ b/packages/bbui/src/Form/Input.svench @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/bbui/src/Form/Multiselect.svelte b/packages/bbui/src/Form/Multiselect.svelte new file mode 100644 index 0000000000..924fd4b2b5 --- /dev/null +++ b/packages/bbui/src/Form/Multiselect.svelte @@ -0,0 +1,324 @@ + + +{#if label} + +{/if} +
+
+
+ {#each selectedOptions as option} +
+ {option.name} +
remove(option.value)}> + + + +
+
+ {/each} + {#if !value || !value.length} + {#if placeholder && placeholder.length} +
{placeholder}
+ {:else} +
 
+ {/if} + {/if} +
+
+ + + + {#if optionsVisible} + +
    showOptions(false)} + transition:fly={{ duration: 200, y: 5 }} + on:mousedown|preventDefault={handleOptionMousedown}> + {#each options as option} +
  • + {option.name} +
  • + {/each} + {#if !options.length} +
  • No results
  • + {/if} +
+
+ {/if} +
+ + diff --git a/packages/bbui/src/Form/Multiselect.svench b/packages/bbui/src/Form/Multiselect.svench new file mode 100644 index 0000000000..86ad0de529 --- /dev/null +++ b/packages/bbui/src/Form/Multiselect.svench @@ -0,0 +1,63 @@ + + + + + {#each options as option} + + {/each} + + + + +
+ + {#each options as option} + + {/each} + +
+
+ + + + {#each options as option} + + {/each} + + + + + + {#each options as option} + + {/each} + + + + + + {#each options as option} + + {/each} + + + + + + {#each options as option} + + {/each} + + + + diff --git a/packages/bbui/src/Form/Radio.svelte b/packages/bbui/src/Form/Radio.svelte new file mode 100644 index 0000000000..b4a2637803 --- /dev/null +++ b/packages/bbui/src/Form/Radio.svelte @@ -0,0 +1,140 @@ + + +
+ +
+
+
+
+
+ +
+ + diff --git a/packages/bbui/src/Form/Radio.svench.svx b/packages/bbui/src/Form/Radio.svench.svx new file mode 100644 index 0000000000..20d3c58c99 --- /dev/null +++ b/packages/bbui/src/Form/Radio.svench.svx @@ -0,0 +1,64 @@ + + + ## Multiple checkboxes + Use an array and an each block to use the radio button. +```svelte + + +{#each menu as flavour} + +{/each} +``` + + + +
+ {#each menu as flavour} + + + + {/each} +
+
+ + +
+ {#each menu as flavour} + + + + {/each} +
+
+ + \ No newline at end of file diff --git a/packages/bbui/src/Form/RichText.svelte b/packages/bbui/src/Form/RichText.svelte new file mode 100644 index 0000000000..ab4b9c3d99 --- /dev/null +++ b/packages/bbui/src/Form/RichText.svelte @@ -0,0 +1,59 @@ + + + + {#if mergedOptions.theme !== 'snow'} + + {/if} + + +
+
+
diff --git a/packages/bbui/src/Form/RichText.svench.svx b/packages/bbui/src/Form/RichText.svench.svx new file mode 100644 index 0000000000..0a0a858866 --- /dev/null +++ b/packages/bbui/src/Form/RichText.svench.svx @@ -0,0 +1,40 @@ + + +### Rich Text Component + +This component uses the QuillJS library to add Rich Text editing functionality. + +It exposes a content variable that you can bind to in order to get Markdown out of the component. + +As well as the content you can also pass in an option object that looks like so: + +```js +let options = { + modules: { + toolbar: [ + [{ header: [1, 2, 3, false] }], + ['bold', 'italic', 'underline', 'strike'] + ] + }, + placeholder: 'Type something...', + theme: 'snow' +} +``` + + + + + + + + + + + + diff --git a/packages/bbui/src/Form/Select.svelte b/packages/bbui/src/Form/Select.svelte new file mode 100644 index 0000000000..c245abd245 --- /dev/null +++ b/packages/bbui/src/Form/Select.svelte @@ -0,0 +1,95 @@ + + +
+ {#if label} + + {/if} +
+ +
+ +
+
+
+ + diff --git a/packages/bbui/src/Form/Select.svench b/packages/bbui/src/Form/Select.svench new file mode 100644 index 0000000000..750d7224ca --- /dev/null +++ b/packages/bbui/src/Form/Select.svench @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/bbui/src/Form/Slider.svelte b/packages/bbui/src/Form/Slider.svelte new file mode 100644 index 0000000000..c8a4c0122e --- /dev/null +++ b/packages/bbui/src/Form/Slider.svelte @@ -0,0 +1,88 @@ + + +
+ {#if label} + + {/if} +
+ {#if showRange && min != null}{min}{/if} + + {#if showRange && max != null}{max}{/if} +
+
+ + diff --git a/packages/bbui/src/Form/Slider.svench b/packages/bbui/src/Form/Slider.svench new file mode 100644 index 0000000000..7ed59f78b8 --- /dev/null +++ b/packages/bbui/src/Form/Slider.svench @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/bbui/src/Form/TextArea.svelte b/packages/bbui/src/Form/TextArea.svelte new file mode 100644 index 0000000000..1133a99127 --- /dev/null +++ b/packages/bbui/src/Form/TextArea.svelte @@ -0,0 +1,132 @@ + + +
+ {#if label || edit} +
+ {#if label} + + {/if} + {#if edit} +
+ + +
+ {/if} +
+ {/if} +