Merge branch 'master' of github.com:Budibase/budibase into form-builder
This commit is contained in:
commit
e87b384de5
|
@ -86,6 +86,10 @@ Watch "releases" of this repo to get notified of major updates, and give the sta
|
|||
<img src="https://i.imgur.com/cJpgqm8.png">
|
||||
</p>
|
||||
|
||||
### Stargazers over time
|
||||
|
||||
[![Stargazers over time](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase)
|
||||
|
||||
If you are having issues between updates of the builder, please use the guide [here](https://github.com/Budibase/budibase/blob/master/CONTRIBUTING.md#troubleshooting) to clear down your environment.
|
||||
|
||||
|
||||
|
@ -107,6 +111,8 @@ Budibase wants to make sure anyone can use the tools we develop and we know a lo
|
|||
|
||||
Currently, you can host your apps using Docker or Digital Ocean. The documentation for self-hosting can be found [here](https://docs.budibase.com/self-hosting/introduction-to-self-hosting).
|
||||
|
||||
[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/droplets/new?onboarding_origin=marketplace&i=09038e&fleetUuid=bb04f9c8-1de8-4687-b2ae-1d5177a0535b&appId=77729671&type=applications&size=s-4vcpu-8gb®ion=nyc1&refcode=0caaa6085a82&image=budibase-20-04)
|
||||
|
||||
|
||||
## 🎓 Learning Budibase
|
||||
|
||||
|
@ -154,6 +160,7 @@ If you have a question or would like to talk with other Budibase users, please h
|
|||
|
||||
![Discord Shield](https://discordapp.com/api/guilds/733030666647765003/widget.png?style=shield)
|
||||
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
version: "3"
|
||||
|
||||
# optional ports are specified throughout for more advanced use cases.
|
||||
|
||||
services:
|
||||
app-service:
|
||||
restart: always
|
||||
#build: ./build/server
|
||||
image: budibase/budibase-apps
|
||||
ports:
|
||||
- "${APP_PORT}:4002"
|
||||
|
@ -20,6 +23,7 @@ services:
|
|||
|
||||
worker-service:
|
||||
restart: always
|
||||
#build: ./build/worker
|
||||
image: budibase/budibase-worker
|
||||
ports:
|
||||
- "${WORKER_PORT}:4003"
|
||||
|
@ -62,7 +66,7 @@ services:
|
|||
- ./envoy.yaml:/etc/envoy/envoy.yaml
|
||||
ports:
|
||||
- "${MAIN_PORT}:10000"
|
||||
- "9901:9901"
|
||||
#- "9901:9901"
|
||||
depends_on:
|
||||
- minio-service
|
||||
- worker-service
|
||||
|
@ -77,8 +81,8 @@ services:
|
|||
- COUCHDB_USER=${COUCH_DB_USER}
|
||||
ports:
|
||||
- "${COUCH_DB_PORT}:5984"
|
||||
- "4369:4369"
|
||||
- "9100:9100"
|
||||
#- "4369:4369"
|
||||
#- "9100:9100"
|
||||
volumes:
|
||||
- couchdb_data:/couchdb
|
||||
|
||||
|
|
|
@ -20,6 +20,11 @@ static_resources:
|
|||
route:
|
||||
cluster: app-service
|
||||
prefix_rewrite: "/"
|
||||
|
||||
# special case for presenting our static self hosting page
|
||||
- match: { path: "/" }
|
||||
route:
|
||||
cluster: app-service
|
||||
|
||||
# special case for when API requests are made, can just forward, not to minio
|
||||
- match: { prefix: "/api/" }
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^1.55.2",
|
||||
"@budibase/bbui": "^1.56.0",
|
||||
"@budibase/client": "^0.6.2",
|
||||
"@budibase/colorpicker": "^1.0.1",
|
||||
"@budibase/string-templates": "^0.6.2",
|
||||
|
|
|
@ -2,6 +2,7 @@ import { cloneDeep } from "lodash/fp"
|
|||
import { get } from "svelte/store"
|
||||
import { backendUiStore, store } from "builderStore"
|
||||
import { findComponentPath } from "./storeUtils"
|
||||
import { makePropSafe } from "@budibase/string-templates"
|
||||
import { TableNames } from "../constants"
|
||||
|
||||
// Regex to match all instances of template strings
|
||||
|
@ -128,7 +129,9 @@ export const getContextBindings = (rootComponent, componentId) => {
|
|||
|
||||
contextBindings.push({
|
||||
type: "context",
|
||||
runtimeBinding: `${component._id}.${runtimeBoundKey}`,
|
||||
runtimeBinding: `${makePropSafe(component._id)}.${makePropSafe(
|
||||
runtimeBoundKey
|
||||
)}`,
|
||||
readableBinding: `${component._instanceName}.${table.name}.${key}`,
|
||||
fieldSchema,
|
||||
providerId: component._id,
|
||||
|
@ -197,43 +200,52 @@ export const getSchemaForDatasource = datasource => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Converts a readable data binding into a runtime data binding
|
||||
* utility function for the readableToRuntimeBinding and runtimeToReadableBinding.
|
||||
*/
|
||||
export function readableToRuntimeBinding(bindableProperties, textWithBindings) {
|
||||
function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
|
||||
const convertFrom =
|
||||
convertTo === "runtimeBinding" ? "readableBinding" : "runtimeBinding"
|
||||
if (typeof textWithBindings !== "string") {
|
||||
return textWithBindings
|
||||
}
|
||||
const convertFromProps = bindableProperties
|
||||
.map(el => el[convertFrom])
|
||||
.sort((a, b) => {
|
||||
return b.length - a.length
|
||||
})
|
||||
const boundValues = textWithBindings.match(CAPTURE_VAR_INSIDE_TEMPLATE) || []
|
||||
let result = textWithBindings
|
||||
boundValues.forEach(boundValue => {
|
||||
const binding = bindableProperties.find(({ readableBinding }) => {
|
||||
return boundValue === `{{ ${readableBinding} }}`
|
||||
})
|
||||
if (binding) {
|
||||
result = result.replace(boundValue, `{{ ${binding.runtimeBinding} }}`)
|
||||
for (let boundValue of boundValues) {
|
||||
let newBoundValue = boundValue
|
||||
for (let from of convertFromProps) {
|
||||
if (newBoundValue.includes(from)) {
|
||||
const binding = bindableProperties.find(el => el[convertFrom] === from)
|
||||
newBoundValue = newBoundValue.replace(from, binding[convertTo])
|
||||
}
|
||||
}
|
||||
})
|
||||
result = result.replace(boundValue, newBoundValue)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a readable data binding into a runtime data binding
|
||||
*/
|
||||
export function readableToRuntimeBinding(bindableProperties, textWithBindings) {
|
||||
return bindingReplacement(
|
||||
bindableProperties,
|
||||
textWithBindings,
|
||||
"runtimeBinding"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a runtime data binding into a readable data binding
|
||||
*/
|
||||
export function runtimeToReadableBinding(bindableProperties, textWithBindings) {
|
||||
if (typeof textWithBindings !== "string") {
|
||||
return textWithBindings
|
||||
}
|
||||
const boundValues = textWithBindings.match(CAPTURE_VAR_INSIDE_TEMPLATE) || []
|
||||
let result = textWithBindings
|
||||
boundValues.forEach(boundValue => {
|
||||
const binding = bindableProperties.find(({ runtimeBinding }) => {
|
||||
return boundValue === `{{ ${runtimeBinding} }}`
|
||||
})
|
||||
// Show invalid bindings as invalid rather than a long ID
|
||||
result = result.replace(
|
||||
boundValue,
|
||||
`{{ ${binding?.readableBinding ?? "Invalid binding"} }}`
|
||||
)
|
||||
})
|
||||
return result
|
||||
return bindingReplacement(
|
||||
bindableProperties,
|
||||
textWithBindings,
|
||||
"readableBinding"
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import sanitizeUrl from "./utils/sanitizeUrl"
|
|||
import { rowListUrl } from "./rowListScreen"
|
||||
import { Screen } from "./utils/Screen"
|
||||
import { Component } from "./utils/Component"
|
||||
import { makePropSafe } from "@budibase/string-templates"
|
||||
import {
|
||||
makeBreadcrumbContainer,
|
||||
makeTitleContainer,
|
||||
|
@ -58,8 +59,8 @@ function generateTitleContainer(table, title, formId) {
|
|||
onClick: [
|
||||
{
|
||||
parameters: {
|
||||
rowId: `{{ ${formId}._id }}`,
|
||||
revId: `{{ ${formId}._rev }}`,
|
||||
rowId: `{{ ${makePropSafe(formId)}._id }}`,
|
||||
revId: `{{ ${makePropSafe(formId)}._rev }}`,
|
||||
tableId: table._id,
|
||||
},
|
||||
"##eventHandlerType": "Delete Row",
|
||||
|
@ -113,7 +114,7 @@ const createScreen = table => {
|
|||
const formId = form._json._id
|
||||
const rowDetailId = screen._json.props._id
|
||||
const heading = table.primaryDisplay
|
||||
? `{{ ${rowDetailId}.${table.primaryDisplay} }}`
|
||||
? `{{ ${makePropSafe(rowDetailId)}.${makePropSafe(table.primaryDisplay)} }}`
|
||||
: null
|
||||
form
|
||||
.addChild(makeBreadcrumbContainer(table.name, heading || "Edit"))
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import {
|
||||
TextArea,
|
||||
Label,
|
||||
Input,
|
||||
Heading,
|
||||
Body,
|
||||
Spacer,
|
||||
|
@ -10,6 +11,9 @@
|
|||
Popover,
|
||||
} from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { isValid } from "@budibase/string-templates"
|
||||
import { handlebarsCompletions } from "constants/completions"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = ""
|
||||
|
@ -18,9 +22,14 @@
|
|||
export let align
|
||||
export let popover = null
|
||||
|
||||
let helpers = handlebarsCompletions()
|
||||
let getCaretPosition
|
||||
let validity = true
|
||||
let search = ""
|
||||
|
||||
$: categories = Object.entries(groupBy("category", bindings))
|
||||
$: value && checkValid()
|
||||
$: searchRgx = new RegExp(search, "ig")
|
||||
|
||||
function onClickBinding(binding) {
|
||||
const position = getCaretPosition()
|
||||
|
@ -34,18 +43,27 @@
|
|||
value += toAdd
|
||||
}
|
||||
}
|
||||
|
||||
function checkValid() {
|
||||
validity = isValid(value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<Popover {anchor} {align} bind:this={popover}>
|
||||
<div class="container">
|
||||
<div class="bindings">
|
||||
<Heading small>Available bindings</Heading>
|
||||
<Spacer medium />
|
||||
<Input extraThin placeholder="Search" bind:value={search} />
|
||||
<Spacer medium />
|
||||
<div class="bindings__wrapper">
|
||||
<div class="bindings__list">
|
||||
{#each categories as [categoryName, bindings]}
|
||||
<Heading extraSmall>{categoryName}</Heading>
|
||||
<Spacer extraSmall />
|
||||
{#each bindings as binding}
|
||||
{#each bindings.filter(binding =>
|
||||
binding.label.match(searchRgx)
|
||||
) as binding}
|
||||
<div class="binding" on:click={() => onClickBinding(binding)}>
|
||||
<span class="binding__label">{binding.label}</span>
|
||||
<span class="binding__type">{binding.type}</span>
|
||||
|
@ -56,6 +74,17 @@
|
|||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
<Heading extraSmall>Helpers</Heading>
|
||||
<Spacer extraSmall />
|
||||
{#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper}
|
||||
<div class="binding" on:click={() => onClickBinding(helper)}>
|
||||
<span class="binding__label">{helper.label}</span>
|
||||
<br />
|
||||
<div class="binding__description">
|
||||
{@html helper.description || ''}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -70,11 +99,20 @@
|
|||
bind:getCaretPosition
|
||||
bind:value
|
||||
placeholder="Add options from the left, type text, or do both" />
|
||||
{#if !validity}
|
||||
<p class="syntax-error">
|
||||
Current Handlebars syntax is invalid, please check the guide
|
||||
<a href="https://handlebarsjs.com/guide/">here</a>
|
||||
for more details.
|
||||
</p>
|
||||
{/if}
|
||||
<div class="controls">
|
||||
<a href="https://docs.budibase.com/design/binding">
|
||||
<Body small>Learn more about binding</Body>
|
||||
</a>
|
||||
<Button on:click={popover.hide} primary>Done</Button>
|
||||
<Button on:click={popover.hide} disabled={!validity} primary>
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -152,4 +190,14 @@
|
|||
align-items: center;
|
||||
margin-top: var(--spacing-m);
|
||||
}
|
||||
|
||||
.syntax-error {
|
||||
color: var(--red);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.syntax-error a {
|
||||
color: var(--red);
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
let permissions = []
|
||||
let selectedRole = {}
|
||||
let errors = []
|
||||
let builtInRoles = ['Admin', 'Power', 'Basic', 'Public']
|
||||
let builtInRoles = ["Admin", "Power", "Basic", "Public"]
|
||||
$: selectedRoleId = selectedRole._id
|
||||
$: otherRoles = $backendUiStore.roles.filter(
|
||||
role => role._id !== selectedRoleId
|
||||
|
@ -103,7 +103,11 @@
|
|||
{/each}
|
||||
</Select>
|
||||
{#if selectedRole}
|
||||
<Input label="Name" bind:value={selectedRole.name} thin disabled={builtInRoles.includes(selectedRole.name)}/>
|
||||
<Input
|
||||
label="Name"
|
||||
bind:value={selectedRole.name}
|
||||
thin
|
||||
disabled={builtInRoles.includes(selectedRole.name)} />
|
||||
<Select
|
||||
thin
|
||||
secondary
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import api from "builderStore/api"
|
||||
import { Button, Select } from "@budibase/bbui"
|
||||
import download from "downloadjs"
|
||||
|
||||
const FORMATS = [
|
||||
{
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
|
||||
.icon {
|
||||
right: 2px;
|
||||
top: 2px;
|
||||
top: 26px;
|
||||
bottom: 2px;
|
||||
position: absolute;
|
||||
align-items: center;
|
||||
|
|
|
@ -1,59 +1,127 @@
|
|||
<script>
|
||||
import groupBy from "lodash/fp/groupBy"
|
||||
import { Button, TextArea, Drawer, Heading, Spacer } from "@budibase/bbui"
|
||||
import { Input, TextArea, Heading, Spacer, Label } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { isValid } from "@budibase/string-templates"
|
||||
import {
|
||||
getBindableProperties,
|
||||
readableToRuntimeBinding,
|
||||
} from "builderStore/dataBinding"
|
||||
import { currentAsset, store } from "../../../builderStore"
|
||||
import { handlebarsCompletions } from "constants/completions"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let bindableProperties
|
||||
export let value = ""
|
||||
export let bindingDrawer
|
||||
export let valid = true
|
||||
|
||||
let originalValue = value
|
||||
let helpers = handlebarsCompletions()
|
||||
let getCaretPosition
|
||||
let search = ""
|
||||
|
||||
$: value && checkValid()
|
||||
$: bindableProperties = getBindableProperties(
|
||||
$currentAsset.props,
|
||||
$store.selectedComponentId
|
||||
)
|
||||
$: dispatch("update", value)
|
||||
$: ({ instance, context } = groupBy("type", bindableProperties))
|
||||
$: searchRgx = new RegExp(search, "ig")
|
||||
|
||||
function checkValid() {
|
||||
// TODO: need to convert the value to the runtime binding
|
||||
const runtimeBinding = readableToRuntimeBinding(bindableProperties, value)
|
||||
valid = isValid(runtimeBinding)
|
||||
}
|
||||
|
||||
function addToText(readableBinding) {
|
||||
value = `${value || ""}{{ ${readableBinding} }}`
|
||||
const position = getCaretPosition()
|
||||
const toAdd = `{{ ${readableBinding} }}`
|
||||
if (position.start) {
|
||||
value =
|
||||
value.substring(0, position.start) +
|
||||
toAdd +
|
||||
value.substring(position.end, value.length)
|
||||
} else {
|
||||
value += toAdd
|
||||
}
|
||||
}
|
||||
let originalValue = value
|
||||
|
||||
$: dispatch("update", value)
|
||||
|
||||
export function cancel() {
|
||||
dispatch("update", originalValue)
|
||||
bindingDrawer.close()
|
||||
}
|
||||
|
||||
$: ({ instance, context } = groupBy("type", bindableProperties))
|
||||
function updateValue({ detail }) {
|
||||
value = detail.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="drawer-contents">
|
||||
<div class="container" data-cy="binding-dropdown-modal">
|
||||
<div class="list">
|
||||
<Input extraThin placeholder="Search" bind:value={search} />
|
||||
<Spacer medium />
|
||||
{#if context}
|
||||
<Heading extraSmall>Columns</Heading>
|
||||
<Spacer small />
|
||||
<ul>
|
||||
{#each context as { readableBinding }}
|
||||
{#each context.filter(context =>
|
||||
context.readableBinding.match(searchRgx)
|
||||
) as { readableBinding }}
|
||||
<li on:click={() => addToText(readableBinding)}>
|
||||
{readableBinding}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
<Spacer small />
|
||||
{#if instance}
|
||||
<Heading extraSmall>Components</Heading>
|
||||
<Spacer small />
|
||||
<ul>
|
||||
{#each instance as { readableBinding }}
|
||||
{#each instance.filter(instance =>
|
||||
instance.readableBinding.match(searchRgx)
|
||||
) as { readableBinding }}
|
||||
<li on:click={() => addToText(readableBinding)}>
|
||||
{readableBinding}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
<Spacer small />
|
||||
<Heading extraSmall>Helpers</Heading>
|
||||
<Spacer small />
|
||||
<ul>
|
||||
{#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper}
|
||||
<li on:click={() => addToText(helper.text)}>
|
||||
<div>
|
||||
<Label extraSmall>{helper.displayText}</Label>
|
||||
<div class="description">
|
||||
{@html helper.description}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="text">
|
||||
<TextArea
|
||||
bind:getCaretPosition
|
||||
thin
|
||||
bind:value
|
||||
placeholder="Add text, or click the objects on the left to add them to
|
||||
the textbox." />
|
||||
{#if !valid}
|
||||
<p class="syntax-error">
|
||||
Current Handlebars syntax is invalid, please check the guide
|
||||
<a href="https://handlebarsjs.com/guide/">here</a>
|
||||
for more details.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -114,4 +182,27 @@
|
|||
height: 40vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.syntax-error {
|
||||
padding-top: var(--spacing-m);
|
||||
color: var(--red);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.syntax-error a {
|
||||
color: var(--red);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.description :global(p) {
|
||||
color: var(--grey-7);
|
||||
}
|
||||
|
||||
.description :global(p:hover) {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.description :global(p a) {
|
||||
color: var(--grey-7);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
let bindingDrawer
|
||||
let temporaryBindableValue = value
|
||||
let anchor
|
||||
let valid
|
||||
|
||||
$: bindableProperties = getBindableProperties(
|
||||
$currentAsset.props,
|
||||
|
@ -79,7 +80,7 @@
|
|||
class="icon"
|
||||
data-cy={`${key}-binding-button`}
|
||||
on:click={bindingDrawer.show}>
|
||||
<Icon name="edit" />
|
||||
<Icon name="lightning" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -90,10 +91,11 @@
|
|||
</Body>
|
||||
</div>
|
||||
<heading slot="buttons">
|
||||
<Button thin blue on:click={handleClose}>Save</Button>
|
||||
<Button thin blue disabled={!valid} on:click={handleClose}>Save</Button>
|
||||
</heading>
|
||||
<div slot="body">
|
||||
<BindingPanel
|
||||
bind:valid
|
||||
value={safeValue}
|
||||
close={handleClose}
|
||||
on:update={e => (temporaryBindableValue = e.detail)}
|
||||
|
@ -142,7 +144,7 @@
|
|||
border-top-right-radius: var(--border-radius-m);
|
||||
border-bottom-right-radius: var(--border-radius-m);
|
||||
color: var(--grey-7);
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.icon:hover {
|
||||
color: var(--ink);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import CodeMirror from "./codemirror"
|
||||
import { onMount, createEventDispatcher } from "svelte"
|
||||
import { themeStore } from "builderStore"
|
||||
import { handlebarsCompletions } from "constants/completions"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
@ -15,6 +16,14 @@
|
|||
export let lineNumbers = true
|
||||
export let tab = true
|
||||
export let mode
|
||||
// export let parameters = []
|
||||
|
||||
let completions = handlebarsCompletions()
|
||||
|
||||
// $: completions = parameters.map(param => ({
|
||||
// text: `{{ ${param.name} }}`,
|
||||
// displayText: param.name,
|
||||
// }))
|
||||
|
||||
let width
|
||||
let height
|
||||
|
@ -109,6 +118,7 @@
|
|||
mode: modes[mode] || {
|
||||
name: mode,
|
||||
},
|
||||
|
||||
readOnly,
|
||||
autoCloseBrackets: true,
|
||||
autoCloseTags: true,
|
||||
|
@ -136,6 +146,18 @@
|
|||
}
|
||||
})
|
||||
|
||||
// editor.on("cursorActivity", function() {
|
||||
// editor.showHint({
|
||||
// hint: function() {
|
||||
// return {
|
||||
// from: editor.getDoc().getCursor(),
|
||||
// to: editor.getDoc().getCursor(),
|
||||
// list: completions,
|
||||
// }
|
||||
// },
|
||||
// })
|
||||
// })
|
||||
|
||||
if (first) await sleep(50)
|
||||
editor.refresh()
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import CodeMirror from "codemirror"
|
||||
import "codemirror/lib/codemirror.css"
|
||||
import "codemirror/theme/tomorrow-night-eighties.css"
|
||||
import "codemirror/addon/hint/show-hint.css"
|
||||
import "codemirror/theme/neo.css"
|
||||
import "codemirror/mode/sql/sql"
|
||||
import "codemirror/mode/css/css"
|
||||
import "codemirror/mode/handlebars/handlebars"
|
||||
import "codemirror/mode/javascript/javascript"
|
||||
import "codemirror/addon/hint/show-hint"
|
||||
|
||||
export default CodeMirror
|
||||
|
|
|
@ -28,14 +28,16 @@
|
|||
mode="sql"
|
||||
on:change={updateQuery}
|
||||
readOnly={!editable}
|
||||
value={query.fields.sql} />
|
||||
value={query.fields.sql}
|
||||
parameters={query.parameters} />
|
||||
{:else if schema.type === QueryTypes.JSON}
|
||||
<Editor
|
||||
label="Query"
|
||||
mode="json"
|
||||
on:change={updateQuery}
|
||||
readOnly={!editable}
|
||||
value={query.fields.json} />
|
||||
value={query.fields.json}
|
||||
parameters={query.parameters} />
|
||||
{:else if schema.type === QueryTypes.FIELDS}
|
||||
<FieldsBuilder bind:fields={query.fields} {schema} {editable} />
|
||||
{/if}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import { getManifest } from "@budibase/string-templates"
|
||||
|
||||
export function handlebarsCompletions() {
|
||||
const manifest = getManifest()
|
||||
|
||||
return Object.keys(manifest).flatMap(key =>
|
||||
Object.entries(manifest[key]).map(([helperName, helperConfig]) => ({
|
||||
text: helperName,
|
||||
path: helperName,
|
||||
label: helperName,
|
||||
displayText: helperName,
|
||||
description: helperConfig.description,
|
||||
}))
|
||||
)
|
||||
}
|
|
@ -842,10 +842,10 @@
|
|||
lodash "^4.17.19"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@budibase/bbui@^1.55.2":
|
||||
version "1.55.2"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.55.2.tgz#be636e8b86b7e516a08eb626bb50c4b1f9774bf8"
|
||||
integrity sha512-CevH/olxDFIko9uwYUpUTevPmpShrLwJSR7+wn/JetZERwhTwbLhOXzpsyXaK226qQ8vWhm0U31HRSKI1HwDDg==
|
||||
"@budibase/bbui@^1.56.0":
|
||||
version "1.56.0"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.56.0.tgz#91376f11b43706fd380291e9a5283760996eb62b"
|
||||
integrity sha512-OEFC7MapbJk7Bd7oo79cVOq9BIcK1x8XPHLC1lB2N4hts37IygzB4Egg6JZBD7rh7CqU8ppc4W7wkfQbaXEO1Q==
|
||||
dependencies:
|
||||
markdown-it "^12.0.2"
|
||||
quill "^1.3.7"
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
"rollup-plugin-node-globals": "^1.4.0",
|
||||
"rollup-plugin-node-resolve": "^5.2.0",
|
||||
"rollup-plugin-svelte": "^6.1.1",
|
||||
"rollup-plugin-terser": "^4.0.4",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"svelte": "^3.30.0",
|
||||
"svelte-jester": "^1.0.6"
|
||||
},
|
||||
|
|
|
@ -2,6 +2,7 @@ import commonjs from "@rollup/plugin-commonjs"
|
|||
import resolve from "@rollup/plugin-node-resolve"
|
||||
import builtins from "rollup-plugin-node-builtins"
|
||||
import svelte from "rollup-plugin-svelte"
|
||||
import { terser } from "rollup-plugin-terser"
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH
|
||||
|
||||
|
@ -25,6 +26,7 @@ export default {
|
|||
}),
|
||||
commonjs(),
|
||||
builtins(),
|
||||
production && terser(),
|
||||
],
|
||||
watch: {
|
||||
clearScreen: false,
|
||||
|
|
|
@ -9,6 +9,13 @@
|
|||
dependencies:
|
||||
"@babel/highlight" "^7.10.4"
|
||||
|
||||
"@babel/code-frame@^7.10.4":
|
||||
version "7.12.11"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
|
||||
integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.10.4"
|
||||
|
||||
"@babel/helper-validator-identifier@^7.10.4":
|
||||
version "7.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2"
|
||||
|
@ -373,7 +380,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
|
|||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@^2.19.0:
|
||||
commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
@ -847,6 +854,11 @@ has-flag@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
has-flag@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||
|
||||
has-symbols@^1.0.0, has-symbols@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
|
||||
|
@ -1097,13 +1109,14 @@ isstream@~0.1.2:
|
|||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
|
||||
|
||||
jest-worker@^24.0.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5"
|
||||
integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==
|
||||
jest-worker@^26.2.1:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
|
||||
integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
merge-stream "^2.0.0"
|
||||
supports-color "^6.1.0"
|
||||
supports-color "^7.0.0"
|
||||
|
||||
js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
@ -1564,7 +1577,7 @@ qs@~6.5.2:
|
|||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
||||
|
||||
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
|
||||
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
|
||||
|
@ -1744,15 +1757,15 @@ rollup-plugin-svelte@^6.1.1:
|
|||
rollup-pluginutils "^2.8.2"
|
||||
sourcemap-codec "^1.4.8"
|
||||
|
||||
rollup-plugin-terser@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-4.0.4.tgz#6f661ef284fa7c27963d242601691dc3d23f994e"
|
||||
integrity sha512-wPANT5XKVJJ8RDUN0+wIr7UPd0lIXBo4UdJ59VmlPCtlFsE20AM+14pe+tk7YunCsWEiuzkDBY3QIkSCjtrPXg==
|
||||
rollup-plugin-terser@^7.0.2:
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
|
||||
integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
jest-worker "^24.0.0"
|
||||
serialize-javascript "^1.6.1"
|
||||
terser "^3.14.1"
|
||||
"@babel/code-frame" "^7.10.4"
|
||||
jest-worker "^26.2.1"
|
||||
serialize-javascript "^4.0.0"
|
||||
terser "^5.0.0"
|
||||
|
||||
rollup-pluginutils@^2.3.1, rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2:
|
||||
version "2.8.2"
|
||||
|
@ -1795,10 +1808,12 @@ semver@~2.3.1:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-2.3.2.tgz#b9848f25d6cf36333073ec9ef8856d42f1233e52"
|
||||
integrity sha1-uYSPJdbPNjMwc+ye+IVtQvEjPlI=
|
||||
|
||||
serialize-javascript@^1.6.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb"
|
||||
integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==
|
||||
serialize-javascript@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
|
||||
integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
sha.js@^2.4.0, sha.js@^2.4.8:
|
||||
version "2.4.11"
|
||||
|
@ -1823,7 +1838,7 @@ side-channel@^1.0.2:
|
|||
es-abstract "^1.18.0-next.0"
|
||||
object-inspect "^1.8.0"
|
||||
|
||||
source-map-support@~0.5.10:
|
||||
source-map-support@~0.5.19:
|
||||
version "0.5.19"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
|
||||
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
|
||||
|
@ -1836,6 +1851,11 @@ source-map@^0.6.0, source-map@~0.6.1:
|
|||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@~0.7.2:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||
|
||||
sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8:
|
||||
version "1.4.8"
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||
|
@ -1908,12 +1928,12 @@ supports-color@^5.3.0:
|
|||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
|
||||
integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
|
||||
supports-color@^7.0.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
has-flag "^4.0.0"
|
||||
|
||||
svelte-jester@^1.0.6:
|
||||
version "1.1.5"
|
||||
|
@ -1939,14 +1959,14 @@ symbol-tree@^3.2.4:
|
|||
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
|
||||
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
|
||||
|
||||
terser@^3.14.1:
|
||||
version "3.17.0"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2"
|
||||
integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==
|
||||
terser@^5.0.0:
|
||||
version "5.5.1"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.1.tgz#540caa25139d6f496fdea056e414284886fb2289"
|
||||
integrity sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==
|
||||
dependencies:
|
||||
commander "^2.19.0"
|
||||
source-map "~0.6.1"
|
||||
source-map-support "~0.5.10"
|
||||
commander "^2.20.0"
|
||||
source-map "~0.7.2"
|
||||
source-map-support "~0.5.19"
|
||||
|
||||
tough-cookie@^2.3.3, tough-cookie@~2.5.0:
|
||||
version "2.5.0"
|
||||
|
|
|
@ -45,7 +45,7 @@ exports.authenticate = async ctx => {
|
|||
expiresIn: "1 day",
|
||||
})
|
||||
|
||||
setCookie(ctx, appId, token)
|
||||
setCookie(ctx, token, appId)
|
||||
|
||||
delete dbUser.password
|
||||
ctx.body = {
|
||||
|
|
|
@ -49,6 +49,17 @@ exports.serveBuilder = async function(ctx) {
|
|||
await send(ctx, ctx.file, { root: ctx.devPath || builderPath })
|
||||
}
|
||||
|
||||
exports.serveSelfHostPage = async function(ctx) {
|
||||
const logo = fs.readFileSync(resolve(__dirname, "selfhost/logo.svg"), "utf8")
|
||||
const hostingHbs = fs.readFileSync(
|
||||
resolve(__dirname, "selfhost/index.hbs"),
|
||||
"utf8"
|
||||
)
|
||||
ctx.body = await processString(hostingHbs, {
|
||||
logo,
|
||||
})
|
||||
}
|
||||
|
||||
exports.uploadFile = async function(ctx) {
|
||||
let files
|
||||
files =
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Budibase self hosting️</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Inter, -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 0 20px;
|
||||
margin: 30px auto;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: clamp(24px, 1.5vw, 30px);
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: grid;
|
||||
background-color: #222222;
|
||||
grid-template-columns: 1fr;
|
||||
align-items: center;
|
||||
padding: 2.5rem 1.75rem;
|
||||
border-radius: 12px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-family: sans-serif;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card h3 b {
|
||||
text-wrap: normal;
|
||||
font-size: 36px;
|
||||
padding-right: 14px;
|
||||
}
|
||||
|
||||
.card p {
|
||||
color: #ffffff;
|
||||
opacity: 0.8;
|
||||
font-size: 18px;
|
||||
text-align: left;
|
||||
line-height: 1.3;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.top-text {
|
||||
text-align: center;
|
||||
color: #707070;
|
||||
margin: 0 0 1.5rem 0;
|
||||
}
|
||||
|
||||
.button {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
background: #4285f4;
|
||||
color: white;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-radius: 6px;
|
||||
max-width: 120px;
|
||||
text-align: center;
|
||||
transition: 200ms background ease;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.info {
|
||||
background: #f5f5f5;
|
||||
padding: 1rem 1rem 1rem 1rem;
|
||||
border: #ccc 1px solid;
|
||||
border-radius: 6px;
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info p {
|
||||
margin-left: 20px;
|
||||
color: #222222;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.info p {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.info svg {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.info a {
|
||||
color: #4285f4;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main">
|
||||
<div class="logo">
|
||||
{{logo}}
|
||||
</div>
|
||||
<h2>Get started with Budibase Self Hosting</h2>
|
||||
<p class="top-text">Use the address <b id="url"></b> in your Builder</p>
|
||||
<div class="card-grid">
|
||||
<div class="card">
|
||||
<h3><b>📚</b>Documentation</h3>
|
||||
<p>
|
||||
Find out more about your self hosted platform.
|
||||
</p>
|
||||
<a class="button"
|
||||
href="https://docs.budibase.com/self-hosting/introduction-to-self-hosting">
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><b>💻</b>Next steps</h3>
|
||||
<p>
|
||||
Find out how to make use of your self hosted Budibase platform.
|
||||
</p>
|
||||
<a class="button"
|
||||
href="https://docs.budibase.com/self-hosting/builder-settings">
|
||||
Next steps
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<svg preserveAspectRatio="xMidYMid meet" height="28px" width="28px" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" stroke="none" class="icon-7f6730be--text-3f89f380">
|
||||
<g>
|
||||
<path d="M12.2 8.98c.06-.01.12-.03.18-.06.06-.02.12-.05.18-.09l.15-.12c.18-.19.29-.45.29-.71 0-.06-.01-.13-.02-.19a.603.603 0 0 0-.06-.19.757.757 0 0 0-.09-.18c-.03-.05-.08-.1-.12-.15-.28-.27-.72-.37-1.09-.21-.13.05-.23.12-.33.21-.04.05-.09.1-.12.15-.04.06-.07.12-.09.18-.03.06-.05.12-.06.19-.01.06-.02.13-.02.19 0 .26.11.52.29.71.1.09.2.16.33.21.12.05.25.08.38.08.06 0 .13-.01.2-.02M13 16v-4a1 1 0 1 0-2 0v4a1 1 0 1 0 2 0M12 3c-4.962 0-9 4.038-9 9 0 4.963 4.038 9 9 9 4.963 0 9-4.037 9-9 0-4.962-4.037-9-9-9m0 20C5.935 23 1 18.065 1 12S5.935 1 12 1c6.066 0 11 4.935 11 11s-4.934 11-11 11" fill-rule="evenodd">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
<p>A <b>Hosting Key</b> will also be required, this can be found in your hosting properties, info found <a href="https://docs.budibase.com/self-hosting/hosting-settings">here</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
window.addEventListener("load", () => {
|
||||
let url = document.URL.split("//")[1]
|
||||
if (url.substring(url.length - 1) === "/") {
|
||||
url = url.substring(0, url.length - 1)
|
||||
}
|
||||
document.getElementById("url").innerHTML = url
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,17 @@
|
|||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 48 48" xml:space="preserve">
|
||||
<style>
|
||||
.st0{fill:#393c44}.st1{fill:#fff}
|
||||
</style>
|
||||
<path class="st0" d="M-152.17-24.17H-56V72h-96.17z"/>
|
||||
<path class="st1" d="M-83.19 48h-41.79c-1.76 0-3.19-1.43-3.19-3.19V3.02c0-1.76 1.43-3.19 3.19-3.19h41.79c1.76 0 3.19 1.43 3.19 3.19v41.79c0 1.76-1.43 3.19-3.19 3.19z"/>
|
||||
<path class="st0" d="M-99.62 12.57v9.94c1.15-1.21 2.59-1.81 4.32-1.81 1.03 0 1.97.19 2.82.58.86.39 1.59.91 2.19 1.57.6.66 1.08 1.43 1.42 2.32.34.89.51 1.84.51 2.85 0 1.03-.18 1.99-.53 2.89-.35.9-.84 1.68-1.47 2.35-.63.67-1.37 1.19-2.23 1.58-.86.39-1.78.58-2.77.58-1.8 0-3.22-.66-4.27-1.97V35h-4.89V12.57h4.9zm6.16 15.54c0-.43-.08-.84-.24-1.23-.16-.39-.39-.72-.68-1.01-.29-.29-.62-.52-1-.69-.38-.17-.79-.26-1.24-.26-.43 0-.84.08-1.22.24-.38.16-.71.39-.99.68-.28.29-.5.63-.68 1.01-.17.39-.26.8-.26 1.23 0 .43.08.84.24 1.22.16.38.39.71.68.99.29.28.63.5 1.01.68.39.17.8.26 1.23.26.43 0 .84-.08 1.22-.24.38-.16.71-.39.99-.68.28-.29.5-.62.68-1 .17-.39.26-.79.26-1.2z"/>
|
||||
<path class="st0" d="M-114.76 12.57v9.94c1.15-1.21 2.59-1.81 4.32-1.81 1.03 0 1.97.19 2.82.58.86.39 1.59.91 2.19 1.57.6.66 1.08 1.43 1.42 2.32.34.89.51 1.84.51 2.85 0 1.03-.18 1.99-.53 2.89-.35.9-.84 1.68-1.47 2.35-.63.67-1.37 1.19-2.23 1.58-.86.39-1.78.58-2.77.58-1.8 0-3.22-.66-4.27-1.97V35h-4.89V12.57h4.9zm6.16 15.54c0-.43-.08-.84-.24-1.23-.16-.39-.39-.72-.68-1.01-.29-.29-.62-.52-1-.69-.38-.17-.79-.26-1.24-.26-.43 0-.84.08-1.22.24-.38.16-.71.39-.99.68-.28.29-.5.63-.68 1.01-.17.39-.26.8-.26 1.23 0 .43.08.84.24 1.22.16.38.39.71.68.99.29.28.63.5 1.01.68.39.17.8.26 1.23.26.43 0 .84-.08 1.22-.24.38-.16.71-.39.99-.68.28-.29.5-.62.68-1 .18-.39.26-.79.26-1.2z"/>
|
||||
<path d="M44.81 159H3.02c-1.76 0-3.19-1.43-3.19-3.19v-41.79c0-1.76 1.43-3.19 3.19-3.19h41.79c1.76 0 3.19 1.43 3.19 3.19v41.79c0 1.76-1.43 3.19-3.19 3.19z" fill="#4285f4"/>
|
||||
<path class="st1" d="M28.38 123.57v9.94c1.15-1.21 2.59-1.81 4.32-1.81 1.03 0 1.97.19 2.82.58.86.39 1.59.91 2.19 1.57.6.66 1.08 1.43 1.42 2.32.34.89.51 1.84.51 2.85 0 1.03-.18 1.99-.53 2.89-.35.9-.84 1.68-1.47 2.35-.63.67-1.37 1.19-2.23 1.58-.86.39-1.78.58-2.77.58-1.8 0-3.22-.66-4.27-1.97V146h-4.89v-22.43h4.9zm6.16 15.54c0-.43-.08-.84-.24-1.23-.16-.39-.39-.72-.68-1.01-.29-.29-.62-.52-1-.69-.38-.17-.79-.26-1.24-.26-.43 0-.84.08-1.22.24-.38.16-.71.39-.99.68-.28.29-.5.63-.68 1.01-.17.39-.26.8-.26 1.23 0 .43.08.84.24 1.22.16.38.39.71.68.99.29.28.63.5 1.01.68.39.17.8.26 1.23.26.43 0 .84-.08 1.22-.24.38-.16.71-.39.99-.68.28-.29.5-.62.68-1 .17-.39.26-.79.26-1.2z"/>
|
||||
<path class="st1" d="M13.24 123.57v9.94c1.15-1.21 2.59-1.81 4.32-1.81 1.03 0 1.97.19 2.82.58.86.39 1.59.91 2.19 1.57.6.66 1.08 1.43 1.42 2.32.34.89.51 1.84.51 2.85 0 1.03-.18 1.99-.53 2.89-.35.9-.84 1.68-1.47 2.35-.63.67-1.37 1.19-2.23 1.58-.86.39-1.78.58-2.77.58-1.8 0-3.22-.66-4.27-1.97V146H8.35v-22.43h4.89zm6.16 15.54c0-.43-.08-.84-.24-1.23-.16-.39-.39-.72-.68-1.01-.29-.29-.62-.52-1-.69-.38-.17-.79-.26-1.24-.26-.43 0-.84.08-1.22.24-.38.16-.71.39-.99.68-.28.29-.5.63-.68 1.01-.17.39-.26.8-.26 1.23 0 .43.08.84.24 1.22.16.38.39.71.68.99.29.28.63.5 1.01.68.39.17.8.26 1.23.26.43 0 .84-.08 1.22-.24.38-.16.71-.39.99-.68.28-.29.5-.62.68-1 .18-.39.26-.79.26-1.2z"/>
|
||||
<g>
|
||||
<path class="st0" d="M44 48H4c-2.21 0-4-1.79-4-4V4c0-2.21 1.79-4 4-4h40c2.21 0 4 1.79 4 4v40c0 2.21-1.79 4-4 4z"/>
|
||||
<path class="st1" d="M28.48 12v10.44c1.18-1.27 2.65-1.9 4.42-1.9 1.05 0 2.01.2 2.89.61.87.41 1.62.96 2.24 1.65.62.69 1.1 1.5 1.45 2.44.35.94.52 1.93.52 2.99 0 1.08-.18 2.09-.54 3.04-.36.95-.86 1.77-1.51 2.47-.64.7-1.4 1.25-2.28 1.66-.87.4-1.81.6-2.83.6-1.84 0-3.3-.69-4.37-2.07v1.62h-5V12h5.01zm6.3 16.31c0-.45-.08-.88-.25-1.29-.17-.41-.4-.76-.69-1.06-.3-.3-.64-.54-1.02-.72-.39-.18-.81-.27-1.27-.27-.44 0-.86.09-1.24.26-.39.17-.72.41-1.01.71-.29.3-.52.66-.69 1.06-.18.41-.26.84-.26 1.29s.08.88.25 1.28c.17.4.4.74.69 1.04.29.29.64.53 1.04.71.4.18.82.27 1.26.27.44 0 .86-.09 1.24-.26.39-.17.72-.41 1.01-.71.29-.3.52-.65.69-1.05.16-.41.25-.82.25-1.26z"/>
|
||||
<path class="st1" d="M13 12v10.44c1.18-1.27 2.65-1.9 4.42-1.9 1.05 0 2.01.2 2.89.61.87.41 1.62.96 2.24 1.65.62.69 1.1 1.5 1.45 2.44.35.94.52 1.93.52 2.99 0 1.08-.18 2.09-.54 3.04-.36.95-.86 1.77-1.51 2.47-.64.7-1.4 1.25-2.28 1.66-.87.4-1.81.6-2.82.6-1.84 0-3.3-.69-4.37-2.07v1.62H8V12h5zm6.3 16.31c0-.45-.08-.88-.25-1.29-.17-.41-.4-.76-.69-1.06-.3-.3-.64-.54-1.02-.72-.39-.18-.81-.27-1.27-.27-.44 0-.86.09-1.24.26-.39.17-.72.41-1.01.71-.29.3-.52.66-.69 1.06-.18.41-.26.84-.26 1.29s.08.88.25 1.28c.17.4.4.74.69 1.04.29.29.64.53 1.04.71.4.18.82.27 1.26.27.44 0 .86-.09 1.24-.26.39-.17.72-.41 1.01-.71.29-.3.52-.65.69-1.05.16-.41.25-.82.25-1.26z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
|
@ -65,6 +65,8 @@ for (let route of mainRoutes) {
|
|||
router.use(staticRoutes.routes())
|
||||
router.use(staticRoutes.allowedMethods())
|
||||
|
||||
router.redirect("/", "/_builder")
|
||||
if (!env.SELF_HOSTED && !env.CLOUD) {
|
||||
router.redirect("/", "/_builder")
|
||||
}
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -23,6 +23,10 @@ if (env.NODE_ENV !== "production") {
|
|||
router.get("/_builder/:file*", controller.serveBuilder)
|
||||
}
|
||||
|
||||
if (env.SELF_HOSTED) {
|
||||
router.get("/", controller.serveSelfHostPage)
|
||||
}
|
||||
|
||||
router
|
||||
.post(
|
||||
"/api/attachments/process",
|
||||
|
|
|
@ -2,7 +2,13 @@ const jwt = require("jsonwebtoken")
|
|||
const STATUS_CODES = require("../utilities/statusCodes")
|
||||
const { getRole, BUILTIN_ROLES } = require("../utilities/security/roles")
|
||||
const { AuthTypes } = require("../constants")
|
||||
const { getAppId, getCookieName, setCookie, isClient } = require("../utilities")
|
||||
const {
|
||||
getAppId,
|
||||
getCookieName,
|
||||
clearCookie,
|
||||
setCookie,
|
||||
isClient,
|
||||
} = require("../utilities")
|
||||
|
||||
module.exports = async (ctx, next) => {
|
||||
if (ctx.path === "/_builder") {
|
||||
|
@ -15,16 +21,18 @@ module.exports = async (ctx, next) => {
|
|||
let appId = getAppId(ctx)
|
||||
const cookieAppId = ctx.cookies.get(getCookieName("currentapp"))
|
||||
if (appId && cookieAppId !== appId) {
|
||||
setCookie(ctx, "currentapp", appId)
|
||||
setCookie(ctx, appId, "currentapp")
|
||||
} else if (cookieAppId) {
|
||||
appId = cookieAppId
|
||||
}
|
||||
|
||||
let token = ctx.cookies.get(getCookieName(appId))
|
||||
let authType = AuthTypes.APP
|
||||
if (!token && !isClient(ctx)) {
|
||||
authType = AuthTypes.BUILDER
|
||||
let token, authType
|
||||
if (!isClient(ctx)) {
|
||||
token = ctx.cookies.get(getCookieName())
|
||||
authType = AuthTypes.BUILDER
|
||||
}
|
||||
if (!token && appId) {
|
||||
token = ctx.cookies.get(getCookieName(appId))
|
||||
authType = AuthTypes.APP
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
|
@ -49,9 +57,13 @@ module.exports = async (ctx, next) => {
|
|||
role: await getRole(appId, jwtPayload.roleId),
|
||||
}
|
||||
} catch (err) {
|
||||
// TODO - this can happen if the JWT secret is changed and can never login
|
||||
// TODO: wipe cookies if they exist
|
||||
ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text)
|
||||
if (authType === AuthTypes.BUILDER) {
|
||||
clearCookie(ctx)
|
||||
ctx.status = 200
|
||||
return
|
||||
} else {
|
||||
ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text)
|
||||
}
|
||||
}
|
||||
|
||||
await next()
|
||||
|
|
|
@ -3,7 +3,7 @@ const env = require("../../environment")
|
|||
const CouchDB = require("../../db")
|
||||
const jwt = require("jsonwebtoken")
|
||||
const { DocumentTypes, SEPARATOR } = require("../../db/utils")
|
||||
const { setCookie } = require("../index")
|
||||
const { setCookie, clearCookie } = require("../index")
|
||||
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||
|
||||
module.exports = async (ctx, appId, version) => {
|
||||
|
@ -20,13 +20,13 @@ module.exports = async (ctx, appId, version) => {
|
|||
})
|
||||
|
||||
// set the builder token
|
||||
setCookie(ctx, "builder", token)
|
||||
setCookie(ctx, "currentapp", appId)
|
||||
setCookie(ctx, token, "builder")
|
||||
setCookie(ctx, appId, "currentapp")
|
||||
// need to clear all app tokens or else unable to use the app in the builder
|
||||
let allDbNames = await CouchDB.allDbs()
|
||||
allDbNames.map(dbName => {
|
||||
if (dbName.startsWith(APP_PREFIX)) {
|
||||
setCookie(ctx, dbName, "")
|
||||
clearCookie(ctx, dbName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -111,16 +111,28 @@ exports.getCookieName = (name = "builder") => {
|
|||
* @param {string} name The name of the cookie to set.
|
||||
* @param {string|object} value The value of cookie which will be set.
|
||||
*/
|
||||
exports.setCookie = (ctx, name, value) => {
|
||||
exports.setCookie = (ctx, value, name = "builder") => {
|
||||
const expires = new Date()
|
||||
expires.setDate(expires.getDate() + 1)
|
||||
|
||||
ctx.cookies.set(exports.getCookieName(name), value, {
|
||||
expires,
|
||||
path: "/",
|
||||
httpOnly: false,
|
||||
overwrite: true,
|
||||
})
|
||||
const cookieName = exports.getCookieName(name)
|
||||
if (!value) {
|
||||
ctx.cookies.set(cookieName)
|
||||
} else {
|
||||
ctx.cookies.set(cookieName, value, {
|
||||
expires,
|
||||
path: "/",
|
||||
httpOnly: false,
|
||||
overwrite: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function, simply calls setCookie with an empty string for value
|
||||
*/
|
||||
exports.clearCookie = (ctx, name) => {
|
||||
exports.setCookie(ctx, "", name)
|
||||
}
|
||||
|
||||
exports.isClient = ctx => {
|
||||
|
|
|
@ -132,18 +132,8 @@
|
|||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Text 1",
|
||||
"key": "text1"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Text 2",
|
||||
"key": "text2"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Text 3",
|
||||
"key": "text3"
|
||||
"label": "Subheading",
|
||||
"key": "subheading"
|
||||
},
|
||||
{
|
||||
"type": "screen",
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
|
||||
export let imageUrl = ""
|
||||
export let heading = ""
|
||||
export let text1 = ""
|
||||
export let text2 = ""
|
||||
export let text3 = ""
|
||||
export let subheading = ""
|
||||
export let destinationUrl = ""
|
||||
|
||||
$: showImage = !!imageUrl
|
||||
|
@ -16,16 +14,16 @@
|
|||
|
||||
<div class="container" use:styleable={$component.styles}>
|
||||
<a href={destinationUrl}>
|
||||
<div class="content">
|
||||
<div class="stackedlist">
|
||||
{#if showImage}
|
||||
<div class="image-block">
|
||||
<img class="image" src={imageUrl} alt="" />
|
||||
</div>
|
||||
{/if}
|
||||
<h2 class="heading">{heading}</h2>
|
||||
<h4 class="text">{text1}</h4>
|
||||
<h4 class="text">{text2}</h4>
|
||||
<h4 class="text3">{text3}</h4>
|
||||
<div class="content">
|
||||
<div class="heading">{heading}</div>
|
||||
<div class="subheading">{subheading}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -34,61 +32,51 @@
|
|||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
.container {
|
||||
padding: 20px;
|
||||
padding: 1rem 1.5rem;
|
||||
min-height: 3rem;
|
||||
display: block;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.stackedlist {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.subheading {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-columns: 120px 300px 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
.image-block {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.image {
|
||||
padding: 2px;
|
||||
max-width: 80px;
|
||||
max-height: 80px;
|
||||
margin-right: 20px;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-size: 24px;
|
||||
margin: 0;
|
||||
min-width: 200px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
.image-block {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
margin-right: 1.5rem;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.text3 {
|
||||
text-align: end;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.text3 {
|
||||
text-align: start;
|
||||
}
|
||||
.image {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
width: 3rem;
|
||||
max-width: 100%;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
border-radius: 99px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
# String templating
|
||||
This package provides a common system for string templating across the Budibase Builder, client and server.
|
||||
The templating is provided through the use of [Handlebars](https://handlebarsjs.com/) an extension of Mustache
|
||||
which is capable of carrying out logic. We have also extended the base Handlebars functionality through the use
|
||||
of a set of helpers provided through the [handlebars-helpers](https://github.com/helpers/handlebars-helpers) package.
|
||||
|
||||
We have not implemented all the helpers provided by the helpers package as some of them provide functionality
|
||||
we felt would not be beneficial. The following collections of helpers have been implemented:
|
||||
1. [Math](https://github.com/helpers/handlebars-helpers/tree/master#math) - a set of useful helpers for
|
||||
carrying out logic pertaining to numbers e.g. `avg`, `add`, `abs` and so on.
|
||||
2. [Array](https://github.com/helpers/handlebars-helpers/tree/master#array) - some very specific helpers
|
||||
for use with arrays, useful for example in automations. Helpers like `first`, `last`, `after` and `join`
|
||||
can be useful for getting particular portions of arrays or turning them into strings.
|
||||
3. [Number](https://github.com/helpers/handlebars-helpers/tree/master#number) - unlike the math helpers these
|
||||
are useful for converting numbers into useful formats for display, e.g. `bytes`, `addCommas` and `toPrecision`.
|
||||
4. [URL](https://github.com/helpers/handlebars-helpers/tree/master#url) - very specific helpers for dealing with URLs,
|
||||
such as `encodeURI`, `escape`, `stripQueryString` and `stripProtocol`. These are primarily useful
|
||||
for building up particular URLs to hit as say part of an automation.
|
||||
5. [String](https://github.com/helpers/handlebars-helpers/tree/master#string) - these helpers are useful for building
|
||||
strings and preparing them for display, e.g. `append`, `camelcase`, `capitalize` and `ellipsis`.
|
||||
6. [Comparison](https://github.com/helpers/handlebars-helpers/tree/master#comparison) - these helpers are mainly for
|
||||
building strings when particular conditions are met, for example `and`, `or`, `gt`, `lt`, `not` and so on. This is a very
|
||||
extensive set of helpers but is mostly as would be expected from a set of logical operators.
|
||||
7. [Date](https://github.com/helpers/helper-date) - last but certainly not least is a moment based date helper, which can
|
||||
format ISO/timestamp based dates into something human-readable. An example of this would be `{{date dateProperty "DD-MM-YYYY"}}`.
|
||||
|
||||
## Date formatting
|
||||
This package uses the standard method for formatting date times, using the following syntax:
|
||||
| Input | Example | Description |
|
||||
| ----- | ------- | ----------- |
|
||||
| YYYY | 2014 | 4 or 2 digit year. Note: Only 4 digit can be parsed on strict mode |
|
||||
| YY | 14 | 2 digit year |
|
||||
| Y | -25 | Year with any number of digits and sign |
|
||||
| Q | 1..4 | Quarter of year. Sets month to first month in quarter. |
|
||||
| M MM | 1..12 | Month number |
|
||||
| MMM MMMM | Jan..December | Month name in locale set by moment.locale() |
|
||||
| D DD | 1..31 | Day of month |
|
||||
| Do | 1st..31st | Day of month with ordinal |
|
||||
| DDD DDDD | 1..365 | Day of year |
|
||||
| X | 1410715640.579 | Unix timestamp |
|
||||
| x | 1410715640579 | Unix ms timestamp |
|
||||
|
||||
## Template format
|
||||
There are two main ways that the templating system can be used, the first is very similar to that which
|
||||
would be produced by Mustache - a single statement:
|
||||
```
|
||||
Hello I'm building a {{uppercase adjective}} string with Handlebars!
|
||||
```
|
||||
In the statement above provided a context of `{adjective: "cool"}` will produce a string of `Hello I'm building a COOL string with Handlebars!`.
|
||||
Here we can see an example of how string helpers can be used to make a string exactly as we need it. These statements are relatively
|
||||
simple; we can also stack helpers as such: `{{ uppercase (remove string "bad") }}` with the use of parenthesis.
|
||||
|
||||
The other type of statement that can be made with the templating system is conditional ones, that appear as such:
|
||||
```
|
||||
Hello I'm building a {{ #gte score "50" }}Great{{ else }}Bad{{ /gte }} string with Handlebars!
|
||||
```
|
||||
In this string we can see that the string `Great` or `Bad` will be inserted depending on the state of the
|
||||
`score` context variable. The comparison, string and array helpers all provide some conditional operators which can be used
|
||||
in this way. There will also be some operators which will be built with a very similar syntax but will produce an
|
||||
iterative operation, like a for each - an example of this would be the `forEach` array helper.
|
||||
|
||||
## Usage
|
||||
Usage of this templating package is through one of the primary functions provided by the package - these functions are
|
||||
as follows:
|
||||
1. `processString` - `async (string, object)` - given a template string and a context object this will build a string
|
||||
using our pre-processors, post-processors and handlebars.
|
||||
2. `processObject` - `async (object, object)` - carries out the functionality provided by `processString` for any string
|
||||
inside the given object. This will recurse deeply into the provided object so for very large objects this could be slow.
|
||||
3. `processStringSync` - `(string, object)` - a reduced functionality processing of strings which is synchronous, like
|
||||
functions provided by Node (e.g. `readdirSync`)
|
||||
4. `processObjectSync` - `(object, object)` - as with the sync'd string, recurses an object to process it synchronously.
|
||||
5. `makePropSafe` - `(string)` - some properties cannot be handled by Handlebars, for example `Table 1` is not valid due
|
||||
to spaces found in the property name. This will update the property name to `[Table 1]` wrapping it in literal
|
||||
specifiers so that it is safe for use in Handlebars. Ideally this function should be called for every level of an object
|
||||
being accessed, for example `[Table 1].[property name]` is the syntax that is required for Handlebars.
|
||||
6. `isValid` - `(string)` - checks the given string for any templates and provides a boolean stating whether it is a valid
|
||||
template.
|
||||
7. `getManifest` - returns the manifest JSON which has been generated for the helpers, describing them and their params.
|
||||
|
||||
## Development
|
||||
This library is built with [Rollup](https://rollupjs.org/guide/en/) as many of the packages built by Budibase are. We have
|
||||
built the string templating package as a UMD so that it can be used by Node and Browser based applications. This package also
|
||||
builds Typescript stubs which when making use of the library will be used by your IDE to provide code completion. The following
|
||||
commands are provided for development purposes:
|
||||
1. `yarn build` - will build the Typescript stubs and the bundle into the `dist` directory.
|
||||
2. `yarn test` - runs the test suite which will check various helpers are still functioning as
|
||||
expected and a few expected use cases.
|
||||
3. `yarn dev:builder` - an internal command which is used by lerna to watch and build any changes
|
||||
to the package as part of the main `yarn dev` of the repo.
|
||||
|
||||
It is also important to note this package is managed in the same manner as all other in the mono-repo,
|
||||
through lerna.
|
|
@ -0,0 +1,986 @@
|
|||
{
|
||||
"math": {
|
||||
"abs": {
|
||||
"args": [
|
||||
"a"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Return the magnitude of <code>a</code>.</p>\n"
|
||||
},
|
||||
"add": {
|
||||
"args": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Return the sum of <code>a</code> plus <code>b</code>.</p>\n"
|
||||
},
|
||||
"avg": {
|
||||
"args": [
|
||||
"array"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Returns the average of all numbers in the given array.</p>\n"
|
||||
},
|
||||
"ceil": {
|
||||
"args": [
|
||||
"value"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Get the <code>Math.ceil()</code> of the given value.</p>\n"
|
||||
},
|
||||
"divide": {
|
||||
"args": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Divide <code>a</code> by <code>b</code></p>\n"
|
||||
},
|
||||
"floor": {
|
||||
"args": [
|
||||
"value"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Get the <code>Math.floor()</code> of the given value.</p>\n"
|
||||
},
|
||||
"minus": {
|
||||
"args": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Return the product of <code>a</code> minus <code>b</code>.</p>\n"
|
||||
},
|
||||
"modulo": {
|
||||
"args": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Get the remainder of a division operation.</p>\n"
|
||||
},
|
||||
"multiply": {
|
||||
"args": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Return the product of <code>a</code> times <code>b</code>.</p>\n"
|
||||
},
|
||||
"plus": {
|
||||
"args": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Add <code>a</code> by <code>b</code>.</p>\n"
|
||||
},
|
||||
"random": {
|
||||
"args": [
|
||||
"min",
|
||||
"max"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Generate a random number between two values</p>\n"
|
||||
},
|
||||
"remainder": {
|
||||
"args": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Get the remainder when <code>a</code> is divided by <code>b</code>.</p>\n"
|
||||
},
|
||||
"round": {
|
||||
"args": [
|
||||
"number"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Round the given number.</p>\n"
|
||||
},
|
||||
"subtract": {
|
||||
"args": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Return the product of <code>a</code> minus <code>b</code>.</p>\n"
|
||||
},
|
||||
"sum": {
|
||||
"args": [
|
||||
"array"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Returns the sum of all numbers in the given array.</p>\n"
|
||||
},
|
||||
"times": {
|
||||
"args": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Multiply number <code>a</code> by number <code>b</code>.</p>\n"
|
||||
}
|
||||
},
|
||||
"array": {
|
||||
"after": {
|
||||
"args": [
|
||||
"array",
|
||||
"n"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Returns all of the items in an array after the specified index. Opposite of <a href=\"#before\">before</a>.</p>\n"
|
||||
},
|
||||
"arrayify": {
|
||||
"args": [
|
||||
"value"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Cast the given <code>value</code> to an array.</p>\n"
|
||||
},
|
||||
"before": {
|
||||
"args": [
|
||||
"array",
|
||||
"n"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Return all of the items in the collection before the specified count. Opposite of <a href=\"#after\">after</a>.</p>\n"
|
||||
},
|
||||
"eachIndex": {
|
||||
"args": [
|
||||
"array",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Iterates the array, listing an item and the index of it.</p>\n"
|
||||
},
|
||||
"filter": {
|
||||
"args": [
|
||||
"array",
|
||||
"value",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that filters the given array and renders the block for values that evaluate to <code>true</code>, otherwise the inverse block is returned.</p>\n"
|
||||
},
|
||||
"first": {
|
||||
"args": [
|
||||
"array",
|
||||
"n"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Returns the first item, or first <code>n</code> items of an array.</p>\n"
|
||||
},
|
||||
"forEach": {
|
||||
"args": [
|
||||
"array"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Iterates over each item in an array and exposes the current item in the array as context to the inner block. In addition to the current array item, the helper exposes the following variables to the inner block: - <code>index</code> - <code>total</code> - <code>isFirst</code> - <code>isLast</code> Also, <code>@index</code> is exposed as a private variable, and additional private variables may be defined as hash arguments.</p>\n"
|
||||
},
|
||||
"inArray": {
|
||||
"args": [
|
||||
"array",
|
||||
"value",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that renders the block if an array has the given <code>value</code>. Optionally specify an inverse block to render when the array does not have the given value.</p>\n"
|
||||
},
|
||||
"isArray": {
|
||||
"args": [
|
||||
"value"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Returns true if <code>value</code> is an es5 array.</p>\n"
|
||||
},
|
||||
"itemAt": {
|
||||
"args": [
|
||||
"array",
|
||||
"idx"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Returns the item from <code>array</code> at index <code>idx</code>.</p>\n"
|
||||
},
|
||||
"join": {
|
||||
"args": [
|
||||
"array",
|
||||
"separator"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Join all elements of array into a string, optionally using a given separator.</p>\n"
|
||||
},
|
||||
"equalsLength": {
|
||||
"args": [
|
||||
"value",
|
||||
"length",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Returns true if the the length of the given <code>value</code> is equal to the given <code>length</code>. Can be used as a block or inline helper.</p>\n"
|
||||
},
|
||||
"last": {
|
||||
"args": [
|
||||
"value",
|
||||
"n"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Returns the last item, or last <code>n</code> items of an array or string. Opposite of <a href=\"#first\">first</a>.</p>\n"
|
||||
},
|
||||
"length": {
|
||||
"args": [
|
||||
"value"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Returns the length of the given string or array.</p>\n"
|
||||
},
|
||||
"lengthEqual": {
|
||||
"args": [
|
||||
"value",
|
||||
"length",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Returns true if the the length of the given <code>value</code> is equal to the given <code>length</code>. Can be used as a block or inline helper.</p>\n"
|
||||
},
|
||||
"map": {
|
||||
"args": [
|
||||
"array",
|
||||
"fn"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Returns a new array, created by calling <code>function</code> on each element of the given <code>array</code>. For example,</p>\n"
|
||||
},
|
||||
"pluck": {
|
||||
"args": [
|
||||
"collection",
|
||||
"prop"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Map over the given object or array or objects and create an array of values from the given <code>prop</code>. Dot-notation may be used (as a string) to get nested properties.</p>\n"
|
||||
},
|
||||
"reverse": {
|
||||
"args": [
|
||||
"value"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Reverse the elements in an array, or the characters in a string.</p>\n"
|
||||
},
|
||||
"some": {
|
||||
"args": [
|
||||
"array",
|
||||
"iter",
|
||||
"provided"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that returns the block if the callback returns true for some value in the given array.</p>\n"
|
||||
},
|
||||
"sort": {
|
||||
"args": [
|
||||
"array",
|
||||
"key"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Sort the given <code>array</code>. If an array of objects is passed, you may optionally pass a <code>key</code> to sort on as the second argument. You may alternatively pass a sorting function as the second argument.</p>\n"
|
||||
},
|
||||
"sortBy": {
|
||||
"args": [
|
||||
"array",
|
||||
"props"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Sort an <code>array</code>. If an array of objects is passed, you may optionally pass a <code>key</code> to sort on as the second argument. You may alternatively pass a sorting function as the second argument.</p>\n"
|
||||
},
|
||||
"withAfter": {
|
||||
"args": [
|
||||
"array",
|
||||
"idx",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Use the items in the array <em>after</em> the specified index as context inside a block. Opposite of <a href=\"#withBefore\">withBefore</a>.</p>\n"
|
||||
},
|
||||
"withBefore": {
|
||||
"args": [
|
||||
"array",
|
||||
"idx",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Use the items in the array <em>before</em> the specified index as context inside a block. Opposite of <a href=\"#withAfter\">withAfter</a>.</p>\n"
|
||||
},
|
||||
"withFirst": {
|
||||
"args": [
|
||||
"array",
|
||||
"idx",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Use the first item in a collection inside a handlebars block expression. Opposite of <a href=\"#withLast\">withLast</a>.</p>\n"
|
||||
},
|
||||
"withGroup": {
|
||||
"args": [
|
||||
"array",
|
||||
"size",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that groups array elements by given group <code>size</code>.</p>\n"
|
||||
},
|
||||
"withLast": {
|
||||
"args": [
|
||||
"array",
|
||||
"idx",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Use the last item or <code>n</code> items in an array as context inside a block. Opposite of <a href=\"#withFirst\">withFirst</a>.</p>\n"
|
||||
},
|
||||
"withSort": {
|
||||
"args": [
|
||||
"array",
|
||||
"prop",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that sorts a collection and exposes the sorted collection as context inside the block.</p>\n"
|
||||
},
|
||||
"unique": {
|
||||
"args": [
|
||||
"array",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Block helper that return an array with all duplicate values removed. Best used along with a <a href=\"#each\">each</a> helper.</p>\n"
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"bytes": {
|
||||
"args": [
|
||||
"number"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Format a number to it's equivalent in bytes. If a string is passed, it's length will be formatted and returned. <strong>Examples:</strong> - <code>'foo' => 3 B</code> - <code>13661855 => 13.66 MB</code> - <code>825399 => 825.39 kB</code> - <code>1396 => 1.4 kB</code></p>\n"
|
||||
},
|
||||
"addCommas": {
|
||||
"args": [
|
||||
"num"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Add commas to numbers</p>\n"
|
||||
},
|
||||
"phoneNumber": {
|
||||
"args": [
|
||||
"num"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Convert a string or number to a formatted phone number.</p>\n"
|
||||
},
|
||||
"toAbbr": {
|
||||
"args": [
|
||||
"number",
|
||||
"precision"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Abbreviate numbers to the given number of <code>precision</code>. This for general numbers, not size in bytes.</p>\n"
|
||||
},
|
||||
"toExponential": {
|
||||
"args": [
|
||||
"number",
|
||||
"fractionDigits"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Returns a string representing the given number in exponential notation.</p>\n"
|
||||
},
|
||||
"toFixed": {
|
||||
"args": [
|
||||
"number",
|
||||
"digits"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Formats the given number using fixed-point notation.</p>\n"
|
||||
},
|
||||
"toFloat": {
|
||||
"args": [
|
||||
"number"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Convert input to a float.</p>\n"
|
||||
},
|
||||
"toInt": {
|
||||
"args": [
|
||||
"number"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Convert input to an integer.</p>\n"
|
||||
},
|
||||
"toPrecision": {
|
||||
"args": [
|
||||
"number",
|
||||
"precision"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Returns a string representing the <code>Number</code> object to the specified precision.</p>\n"
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"encodeURI": {
|
||||
"args": [
|
||||
"str"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Encodes a Uniform Resource Identifier (URI) component by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character.</p>\n"
|
||||
},
|
||||
"escape": {
|
||||
"args": [
|
||||
"str"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Escape the given string by replacing characters with escape sequences. Useful for allowing the string to be used in a URL, etc.</p>\n"
|
||||
},
|
||||
"decodeURI": {
|
||||
"args": [
|
||||
"str"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Decode a Uniform Resource Identifier (URI) component.</p>\n"
|
||||
},
|
||||
"url_encode": {
|
||||
"args": [],
|
||||
"numArgs": 0,
|
||||
"description": "<p>Alias for <a href=\"#encodeuri\">encodeURI</a>.</p>\n"
|
||||
},
|
||||
"url_decode": {
|
||||
"args": [],
|
||||
"numArgs": 0,
|
||||
"description": "<p>Alias for <a href=\"#decodeuri\">decodeURI</a>.</p>\n"
|
||||
},
|
||||
"urlResolve": {
|
||||
"args": [
|
||||
"base",
|
||||
"href"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Take a base URL, and a href URL, and resolve them as a browser would for an anchor tag.</p>\n"
|
||||
},
|
||||
"urlParse": {
|
||||
"args": [
|
||||
"str"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Parses a <code>url</code> string into an object.</p>\n"
|
||||
},
|
||||
"stripQuerystring": {
|
||||
"args": [
|
||||
"url"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Strip the query string from the given <code>url</code>.</p>\n"
|
||||
},
|
||||
"stripProtocol": {
|
||||
"args": [
|
||||
"str"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Strip protocol from a <code>url</code>. Useful for displaying media that may have an 'http' protocol on secure connections.</p>\n"
|
||||
}
|
||||
},
|
||||
"string": {
|
||||
"append": {
|
||||
"args": [
|
||||
"str",
|
||||
"suffix"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Append the specified <code>suffix</code> to the given string.</p>\n"
|
||||
},
|
||||
"camelcase": {
|
||||
"args": [
|
||||
"string"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>camelCase the characters in the given <code>string</code>.</p>\n"
|
||||
},
|
||||
"capitalize": {
|
||||
"args": [
|
||||
"str"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Capitalize the first word in a sentence.</p>\n"
|
||||
},
|
||||
"capitalizeAll": {
|
||||
"args": [
|
||||
"str"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Capitalize all words in a string.</p>\n"
|
||||
},
|
||||
"center": {
|
||||
"args": [
|
||||
"str",
|
||||
"spaces"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Center a string using non-breaking spaces</p>\n"
|
||||
},
|
||||
"chop": {
|
||||
"args": [
|
||||
"string"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Like trim, but removes both extraneous whitespace <strong>and non-word characters</strong> from the beginning and end of a string.</p>\n"
|
||||
},
|
||||
"dashcase": {
|
||||
"args": [
|
||||
"string"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>dash-case the characters in <code>string</code>. Replaces non-word characters and periods with hyphens.</p>\n"
|
||||
},
|
||||
"dotcase": {
|
||||
"args": [
|
||||
"string"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>dot.case the characters in <code>string</code>.</p>\n"
|
||||
},
|
||||
"downcase": {
|
||||
"args": [
|
||||
"string"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Lowercase all of the characters in the given string. Alias for <a href=\"#lowercase\">lowercase</a>.</p>\n"
|
||||
},
|
||||
"ellipsis": {
|
||||
"args": [
|
||||
"str",
|
||||
"length"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Truncates a string to the specified <code>length</code>, and appends it with an elipsis, <code>…</code>.</p>\n"
|
||||
},
|
||||
"hyphenate": {
|
||||
"args": [
|
||||
"str"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Replace spaces in a string with hyphens.</p>\n"
|
||||
},
|
||||
"isString": {
|
||||
"args": [
|
||||
"value"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Return true if <code>value</code> is a string.</p>\n"
|
||||
},
|
||||
"lowercase": {
|
||||
"args": [
|
||||
"str"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Lowercase all characters in the given string.</p>\n"
|
||||
},
|
||||
"occurrences": {
|
||||
"args": [
|
||||
"str",
|
||||
"substring"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Return the number of occurrences of <code>substring</code> within the given <code>string</code>.</p>\n"
|
||||
},
|
||||
"pascalcase": {
|
||||
"args": [
|
||||
"string"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>PascalCase the characters in <code>string</code>.</p>\n"
|
||||
},
|
||||
"pathcase": {
|
||||
"args": [
|
||||
"string"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>path/case the characters in <code>string</code>.</p>\n"
|
||||
},
|
||||
"plusify": {
|
||||
"args": [
|
||||
"str"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Replace spaces in the given string with pluses.</p>\n"
|
||||
},
|
||||
"prepend": {
|
||||
"args": [
|
||||
"str",
|
||||
"prefix"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Prepends the given <code>string</code> with the specified <code>prefix</code>.</p>\n"
|
||||
},
|
||||
"raw": {
|
||||
"args": [
|
||||
"options"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Render a block without processing mustache templates inside the block.</p>\n"
|
||||
},
|
||||
"remove": {
|
||||
"args": [
|
||||
"str",
|
||||
"substring"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Remove all occurrences of <code>substring</code> from the given <code>str</code>.</p>\n"
|
||||
},
|
||||
"removeFirst": {
|
||||
"args": [
|
||||
"str",
|
||||
"substring"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Remove the first occurrence of <code>substring</code> from the given <code>str</code>.</p>\n"
|
||||
},
|
||||
"replace": {
|
||||
"args": [
|
||||
"str",
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Replace all occurrences of substring <code>a</code> with substring <code>b</code>.</p>\n"
|
||||
},
|
||||
"replaceFirst": {
|
||||
"args": [
|
||||
"str",
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Replace the first occurrence of substring <code>a</code> with substring <code>b</code>.</p>\n"
|
||||
},
|
||||
"sentence": {
|
||||
"args": [
|
||||
"str"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Sentence case the given string</p>\n"
|
||||
},
|
||||
"snakecase": {
|
||||
"args": [
|
||||
"string"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>snake_case the characters in the given <code>string</code>.</p>\n"
|
||||
},
|
||||
"split": {
|
||||
"args": [
|
||||
"string"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Split <code>string</code> by the given <code>character</code>.</p>\n"
|
||||
},
|
||||
"startsWith": {
|
||||
"args": [
|
||||
"prefix",
|
||||
"testString",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Tests whether a string begins with the given prefix.</p>\n"
|
||||
},
|
||||
"titleize": {
|
||||
"args": [
|
||||
"str"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Title case the given string.</p>\n"
|
||||
},
|
||||
"trim": {
|
||||
"args": [
|
||||
"string"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Removes extraneous whitespace from the beginning and end of a string.</p>\n"
|
||||
},
|
||||
"trimLeft": {
|
||||
"args": [
|
||||
"string"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Removes extraneous whitespace from the beginning of a string.</p>\n"
|
||||
},
|
||||
"trimRight": {
|
||||
"args": [
|
||||
"string"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Removes extraneous whitespace from the end of a string.</p>\n"
|
||||
},
|
||||
"truncate": {
|
||||
"args": [
|
||||
"str",
|
||||
"limit",
|
||||
"suffix"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Truncate a string to the specified <code>length</code>. Also see <a href=\"#ellipsis\">ellipsis</a>.</p>\n"
|
||||
},
|
||||
"truncateWords": {
|
||||
"args": [
|
||||
"str",
|
||||
"limit",
|
||||
"suffix"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Truncate a string to have the specified number of words. Also see <a href=\"#truncate\">truncate</a>.</p>\n"
|
||||
},
|
||||
"upcase": {
|
||||
"args": [
|
||||
"string"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"description": "<p>Uppercase all of the characters in the given string. Alias for <a href=\"#uppercase\">uppercase</a>.</p>\n"
|
||||
},
|
||||
"uppercase": {
|
||||
"args": [
|
||||
"str",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Uppercase all of the characters in the given string. If used as a block helper it will uppercase the entire block. This helper does not support inverse blocks.</p>\n"
|
||||
}
|
||||
},
|
||||
"comparison": {
|
||||
"and": {
|
||||
"args": [
|
||||
"a",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Helper that renders the block if <strong>both</strong> of the given values are truthy. If an inverse block is specified it will be rendered when falsy. Works as a block helper, inline helper or subexpression.</p>\n"
|
||||
},
|
||||
"compare": {
|
||||
"args": [
|
||||
"a",
|
||||
"operator",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 4,
|
||||
"description": "<p>Render a block when a comparison of the first and third arguments returns true. The second argument is the [arithemetic operator][operators] to use. You may also optionally specify an inverse block to render when falsy.</p>\n"
|
||||
},
|
||||
"contains": {
|
||||
"args": [
|
||||
"collection",
|
||||
"value",
|
||||
"[startIndex=0]",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 4,
|
||||
"description": "<p>Block helper that renders the block if <code>collection</code> has the given <code>value</code>, using strict equality (<code>===</code>) for comparison, otherwise the inverse block is rendered (if specified). If a <code>startIndex</code> is specified and is negative, it is used as the offset from the end of the collection.</p>\n"
|
||||
},
|
||||
"default": {
|
||||
"args": [
|
||||
"value",
|
||||
"defaultValue"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Returns the first value that is not undefined, otherwise the "default" value is returned.</p>\n"
|
||||
},
|
||||
"eq": {
|
||||
"args": [
|
||||
"a",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=""</code> hash argument for the second value.</p>\n"
|
||||
},
|
||||
"gt": {
|
||||
"args": [
|
||||
"a",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>greater than</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=""</code> hash argument for the second value.</p>\n"
|
||||
},
|
||||
"gte": {
|
||||
"args": [
|
||||
"a",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>greater than or equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=""</code> hash argument for the second value.</p>\n"
|
||||
},
|
||||
"has": {
|
||||
"args": [
|
||||
"val",
|
||||
"pattern",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that renders a block if <code>value</code> has <code>pattern</code>. If an inverse block is specified it will be rendered when falsy.</p>\n"
|
||||
},
|
||||
"isFalsey": {
|
||||
"args": [
|
||||
"val",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Returns true if the given <code>value</code> is falsey. Uses the [falsey][] library for comparisons. Please see that library for more information or to report bugs with this helper.</p>\n"
|
||||
},
|
||||
"isTruthy": {
|
||||
"args": [
|
||||
"val",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Returns true if the given <code>value</code> is truthy. Uses the [falsey][] library for comparisons. Please see that library for more information or to report bugs with this helper.</p>\n"
|
||||
},
|
||||
"ifEven": {
|
||||
"args": [
|
||||
"number",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Return true if the given value is an even number.</p>\n"
|
||||
},
|
||||
"ifNth": {
|
||||
"args": [
|
||||
"a",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Conditionally renders a block if the remainder is zero when <code>a</code> operand is divided by <code>b</code>. If an inverse block is specified it will be rendered when the remainder is <strong>not zero</strong>.</p>\n"
|
||||
},
|
||||
"ifOdd": {
|
||||
"args": [
|
||||
"value",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Block helper that renders a block if <code>value</code> is <strong>an odd number</strong>. If an inverse block is specified it will be rendered when falsy.</p>\n"
|
||||
},
|
||||
"is": {
|
||||
"args": [
|
||||
"a",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. Similar to <a href=\"#eq\">eq</a> but does not do strict equality.</p>\n"
|
||||
},
|
||||
"isnt": {
|
||||
"args": [
|
||||
"a",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>not equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. Similar to <a href=\"#unlesseq\">unlessEq</a> but does not use strict equality for comparisons.</p>\n"
|
||||
},
|
||||
"lt": {
|
||||
"args": [
|
||||
"context",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>less than</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=""</code> hash argument for the second value.</p>\n"
|
||||
},
|
||||
"lte": {
|
||||
"args": [
|
||||
"a",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>less than or equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=""</code> hash argument for the second value.</p>\n"
|
||||
},
|
||||
"neither": {
|
||||
"args": [
|
||||
"a",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that renders a block if <strong>neither of</strong> the given values are truthy. If an inverse block is specified it will be rendered when falsy.</p>\n"
|
||||
},
|
||||
"not": {
|
||||
"args": [
|
||||
"val",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Returns true if <code>val</code> is falsey. Works as a block or inline helper.</p>\n"
|
||||
},
|
||||
"or": {
|
||||
"args": [
|
||||
"arguments",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"description": "<p>Block helper that renders a block if <strong>any of</strong> the given values is truthy. If an inverse block is specified it will be rendered when falsy.</p>\n"
|
||||
},
|
||||
"unlessEq": {
|
||||
"args": [
|
||||
"a",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is equal to <code>b</code></strong>.</p>\n"
|
||||
},
|
||||
"unlessGt": {
|
||||
"args": [
|
||||
"a",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is greater than <code>b</code></strong>.</p>\n"
|
||||
},
|
||||
"unlessLt": {
|
||||
"args": [
|
||||
"a",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is less than <code>b</code></strong>.</p>\n"
|
||||
},
|
||||
"unlessGteq": {
|
||||
"args": [
|
||||
"a",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is greater than or equal to <code>b</code></strong>.</p>\n"
|
||||
},
|
||||
"unlessLteq": {
|
||||
"args": [
|
||||
"a",
|
||||
"b",
|
||||
"options"
|
||||
],
|
||||
"numArgs": 3,
|
||||
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is less than or equal to <code>b</code></strong>.</p>\n"
|
||||
}
|
||||
},
|
||||
"date": {
|
||||
"date": {
|
||||
"args": [
|
||||
"datetime",
|
||||
"format"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"example": "{{date now \"YYYY\"}}",
|
||||
"description": "<p>Format a date using moment.js data formatting.</p>\n"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,30 +2,35 @@
|
|||
"name": "@budibase/string-templates",
|
||||
"version": "0.6.2",
|
||||
"description": "Handlebars wrapper for Budibase templating.",
|
||||
"main": "dist/bundle.js",
|
||||
"module": "dist/bundle.js",
|
||||
"main": "src/index.js",
|
||||
"module": "src/index.js",
|
||||
"browser": "dist/bundle.js",
|
||||
"license": "AGPL-3.0",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc && rollup -c",
|
||||
"dev:builder": "tsc && rollup -cw",
|
||||
"test": "jest"
|
||||
"test": "jest",
|
||||
"manifest": "node ./scripts/gen-collection-info.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/handlebars-helpers": "^0.11.1",
|
||||
"handlebars": "^4.7.6",
|
||||
"handlebars-helpers": "^0.10.0",
|
||||
"handlebars-utils": "^1.0.6",
|
||||
"helper-date": "^1.0.1",
|
||||
"lodash": "^4.17.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^17.1.0",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"doctrine": "^3.0.0",
|
||||
"jest": "^26.6.3",
|
||||
"marked": "^1.2.8",
|
||||
"rollup": "^2.36.2",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
"rollup-plugin-node-builtins": "^2.1.2",
|
||||
"rollup-plugin-node-globals": "^1.4.0",
|
||||
"rollup-plugin-node-resolve": "^5.2.0",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"typescript": "^4.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +1,32 @@
|
|||
import commonjs from "rollup-plugin-commonjs"
|
||||
import commonjs from "@rollup/plugin-commonjs"
|
||||
import resolve from "rollup-plugin-node-resolve"
|
||||
import builtins from "rollup-plugin-node-builtins"
|
||||
import globals from "rollup-plugin-node-globals"
|
||||
import json from "@rollup/plugin-json"
|
||||
import { terser } from "rollup-plugin-terser"
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH
|
||||
export default {
|
||||
input: "src/index.js",
|
||||
input: "src/esIndex.js",
|
||||
output: [
|
||||
{
|
||||
sourcemap: true,
|
||||
format: "umd",
|
||||
format: "esm",
|
||||
file: "./dist/bundle.js",
|
||||
name: "string-templates",
|
||||
name: "templates",
|
||||
exports: "named",
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
resolve({
|
||||
mainFields: ["module", "main"],
|
||||
preferBuiltins: true,
|
||||
browser: true,
|
||||
}),
|
||||
commonjs(),
|
||||
globals(),
|
||||
builtins(),
|
||||
production && terser(),
|
||||
json(),
|
||||
],
|
||||
}
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
const HELPER_LIBRARY = "@budibase/handlebars-helpers"
|
||||
const helpers = require(HELPER_LIBRARY)
|
||||
const { HelperFunctionBuiltin } = require("../src/helpers/constants")
|
||||
const fs = require("fs")
|
||||
const doctrine = require("doctrine")
|
||||
const marked = require("marked")
|
||||
|
||||
const DIRECTORY = fs.existsSync("node_modules") ? "." : ".."
|
||||
|
||||
const FILENAME = `${DIRECTORY}/manifest.json`
|
||||
|
||||
/**
|
||||
* full list of supported helpers can be found here:
|
||||
* https://github.com/helpers/handlebars-helpers
|
||||
*/
|
||||
|
||||
const COLLECTIONS = ["math", "array", "number", "url", "string", "comparison"]
|
||||
|
||||
const outputJSON = {}
|
||||
|
||||
function fixSpecialCases(name, obj) {
|
||||
const args = obj.args
|
||||
if (name === "ifNth") {
|
||||
args[0] = "a"
|
||||
args[1] = "b"
|
||||
}
|
||||
if (name === "eachIndex") {
|
||||
obj.description = "Iterates the array, listing an item and the index of it."
|
||||
}
|
||||
if (name === "toFloat") {
|
||||
obj.description = "Convert input to a float."
|
||||
}
|
||||
if (name === "toInt") {
|
||||
obj.description = "Convert input to an integer."
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
function lookForward(lines, funcLines, idx) {
|
||||
const funcLen = funcLines.length
|
||||
for (let i = idx, j = 0; i < idx + funcLen; ++i, j++) {
|
||||
if (!lines[i].includes(funcLines[j])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function getCommentInfo(file, func) {
|
||||
const lines = file.split("\n")
|
||||
const funcLines = func.split("\n")
|
||||
let comment = null
|
||||
for (let idx = 0; idx < lines.length; ++idx) {
|
||||
// from here work back until we have the comment
|
||||
if (lookForward(lines, funcLines, idx)) {
|
||||
let fromIdx = idx
|
||||
let start = 0,
|
||||
end = 0
|
||||
do {
|
||||
if (lines[fromIdx].includes("*/")) {
|
||||
end = fromIdx
|
||||
} else if (lines[fromIdx].includes("/*")) {
|
||||
start = fromIdx
|
||||
}
|
||||
if (start && end) {
|
||||
break
|
||||
}
|
||||
fromIdx--
|
||||
} while (fromIdx > 0)
|
||||
comment = lines.slice(start, end + 1).join("\n")
|
||||
}
|
||||
}
|
||||
if (comment == null) {
|
||||
return { description: "" }
|
||||
}
|
||||
const docs = doctrine.parse(comment, { unwrap: true })
|
||||
// some hacky fixes
|
||||
docs.description = docs.description.replace(/\n/g, " ")
|
||||
docs.description = docs.description.replace(/[ ]{2,}/g, " ")
|
||||
docs.description = docs.description.replace(/is is/g, "is")
|
||||
const examples = docs.tags
|
||||
.filter(el => el.title === "example")
|
||||
.map(el => el.description)
|
||||
const blocks = docs.description.split("```")
|
||||
if (examples.length > 0) {
|
||||
docs.example = examples.join(" ")
|
||||
}
|
||||
docs.description = blocks[0].trim()
|
||||
return docs
|
||||
}
|
||||
|
||||
/**
|
||||
* This script is very specific to purpose, parsing the handlebars-helpers files to attempt to get information about them.
|
||||
*/
|
||||
function run() {
|
||||
const foundNames = []
|
||||
for (let collection of COLLECTIONS) {
|
||||
const collectionFile = fs.readFileSync(
|
||||
`${DIRECTORY}/node_modules/${HELPER_LIBRARY}/lib/${collection}.js`,
|
||||
"utf8"
|
||||
)
|
||||
const collectionInfo = {}
|
||||
// collect information about helper
|
||||
let hbsHelperInfo = helpers[collection]()
|
||||
for (let entry of Object.entries(hbsHelperInfo)) {
|
||||
const name = entry[0]
|
||||
// skip built in functions and ones seen already
|
||||
if (
|
||||
HelperFunctionBuiltin.indexOf(name) !== -1 ||
|
||||
foundNames.indexOf(name) !== -1
|
||||
) {
|
||||
continue
|
||||
}
|
||||
foundNames.push(name)
|
||||
// this is ridiculous, but it parse the function header
|
||||
const fnc = entry[1].toString()
|
||||
const jsDocInfo = getCommentInfo(collectionFile, fnc)
|
||||
let args = jsDocInfo.tags
|
||||
.filter(tag => tag.title === "param")
|
||||
.map(
|
||||
tag =>
|
||||
tag.description &&
|
||||
tag.description
|
||||
.replace(/`/g, "")
|
||||
.split(" ")[0]
|
||||
.trim()
|
||||
)
|
||||
collectionInfo[name] = fixSpecialCases(name, {
|
||||
args,
|
||||
numArgs: args.length,
|
||||
example: jsDocInfo.example || undefined,
|
||||
description: jsDocInfo.description,
|
||||
})
|
||||
}
|
||||
outputJSON[collection] = collectionInfo
|
||||
}
|
||||
// add the date helper
|
||||
outputJSON["date"] = {
|
||||
date: {
|
||||
args: ["datetime", "format"],
|
||||
numArgs: 2,
|
||||
example: '{{date now "YYYY"}}',
|
||||
description: "Format a date using moment.js data formatting.",
|
||||
},
|
||||
}
|
||||
// convert all markdown to HTML
|
||||
for (let collection of Object.values(outputJSON)) {
|
||||
for (let helper of Object.values(collection)) {
|
||||
helper.description = marked(helper.description)
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(FILENAME, JSON.stringify(outputJSON, null, 2))
|
||||
}
|
||||
|
||||
run()
|
|
@ -0,0 +1,12 @@
|
|||
import templates from "./index"
|
||||
|
||||
/**
|
||||
* This file is simply an entrypoint for rollup - makes a lot of cjs problems go away
|
||||
*/
|
||||
export const isValid = templates.isValid
|
||||
export const makePropSafe = templates.makePropSafe
|
||||
export const getManifest = templates.getManifest
|
||||
export const processStringSync = templates.processStringSync
|
||||
export const processObjectSync = templates.processObjectSync
|
||||
export const processString = templates.processString
|
||||
export const processObject = templates.processObject
|
|
@ -18,4 +18,7 @@ module.exports.HelperFunctionBuiltin = [
|
|||
module.exports.HelperFunctionNames = {
|
||||
OBJECT: "object",
|
||||
ALL: "all",
|
||||
LITERAL: "literal",
|
||||
}
|
||||
|
||||
module.exports.LITERAL_MARKER = "%LITERAL%"
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
const helpers = require("handlebars-helpers")
|
||||
const helpers = require("@budibase/handlebars-helpers")
|
||||
const dateHelper = require("helper-date")
|
||||
const { HelperFunctionBuiltin } = require("./constants")
|
||||
|
||||
/**
|
||||
* full list of supported helpers can be found here:
|
||||
* https://github.com/helpers/handlebars-helpers
|
||||
* https://github.com/Budibase/handlebars-helpers
|
||||
*/
|
||||
|
||||
const EXTERNAL_FUNCTION_COLLECTIONS = [
|
||||
|
@ -13,7 +13,7 @@ const EXTERNAL_FUNCTION_COLLECTIONS = [
|
|||
"number",
|
||||
"url",
|
||||
"string",
|
||||
"markdown",
|
||||
"comparison",
|
||||
]
|
||||
|
||||
const DATE_NAME = "date"
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
const Helper = require("./Helper")
|
||||
const { SafeString } = require("handlebars")
|
||||
const externalHandlebars = require("./external")
|
||||
const { HelperFunctionNames, HelperFunctionBuiltin } = require("./constants")
|
||||
const {
|
||||
HelperFunctionNames,
|
||||
HelperFunctionBuiltin,
|
||||
LITERAL_MARKER,
|
||||
} = require("./constants")
|
||||
|
||||
const HTML_SWAPS = {
|
||||
"<": "<",
|
||||
|
@ -27,6 +31,12 @@ const HELPERS = [
|
|||
return HTML_SWAPS[tag] || tag
|
||||
})
|
||||
}),
|
||||
// adds a note for post-processor
|
||||
new Helper(HelperFunctionNames.LITERAL, value => {
|
||||
const type = typeof value
|
||||
const outputVal = type === "object" ? JSON.stringify(value) : value
|
||||
return `{{-${LITERAL_MARKER}-${type}-${outputVal}-}}`
|
||||
}),
|
||||
]
|
||||
|
||||
module.exports.HelperNames = () => {
|
||||
|
|
|
@ -2,7 +2,8 @@ const handlebars = require("handlebars")
|
|||
const { registerAll } = require("./helpers/index")
|
||||
const processors = require("./processors")
|
||||
const { cloneDeep } = require("lodash/fp")
|
||||
const { removeNull } = require("./utilities")
|
||||
const { removeNull, addConstants } = require("./utilities")
|
||||
const manifest = require("../manifest.json")
|
||||
|
||||
const hbsInstance = handlebars.create()
|
||||
registerAll(hbsInstance)
|
||||
|
@ -82,24 +83,52 @@ module.exports.processObjectSync = (object, context) => {
|
|||
* @returns {string} The enriched string, all templates should have been replaced if they can be.
|
||||
*/
|
||||
module.exports.processStringSync = (string, context) => {
|
||||
const clonedContext = removeNull(cloneDeep(context))
|
||||
let clonedContext = removeNull(cloneDeep(context))
|
||||
clonedContext = addConstants(clonedContext)
|
||||
// remove any null/undefined properties
|
||||
if (typeof string !== "string") {
|
||||
throw "Cannot process non-string types."
|
||||
}
|
||||
let template
|
||||
string = processors.preprocess(string)
|
||||
// this does not throw an error when template can't be fulfilled, have to try correct beforehand
|
||||
template = hbsInstance.compile(string)
|
||||
const template = hbsInstance.compile(string)
|
||||
return processors.postprocess(template(clonedContext))
|
||||
}
|
||||
|
||||
/**
|
||||
* Errors can occur if a user of this library attempts to use a helper that has not been added to the system, these errors
|
||||
* can be captured to alert the user of the mistake.
|
||||
* @param {function} handler a function which will be called every time an error occurs when processing a handlebars
|
||||
* statement.
|
||||
* Simple utility function which makes sure that a templating property has been wrapped in literal specifiers correctly.
|
||||
* @param {string} property The property which is to be wrapped.
|
||||
* @returns {string} The wrapped property ready to be added to a templating string.
|
||||
*/
|
||||
module.exports.errorEvents = handler => {
|
||||
hbsInstance.registerHelper("helperMissing", handler)
|
||||
module.exports.makePropSafe = property => {
|
||||
return `[${property}]`.replace("[[", "[").replace("]]", "]")
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a template string contains totally valid syntax (simply tries running it)
|
||||
* @param string The string to test for valid syntax - this may contain no templates and will be considered valid.
|
||||
* @returns {boolean} Whether or not the input string is valid.
|
||||
*/
|
||||
module.exports.isValid = string => {
|
||||
const specialCases = ["isNumber", "expected a number"]
|
||||
// don't really need a real context to check if its valid
|
||||
const context = {}
|
||||
try {
|
||||
hbsInstance.compile(processors.preprocess(string, false))(context)
|
||||
return true
|
||||
} catch (err) {
|
||||
const msg = err ? err.message : ""
|
||||
const foundCase = specialCases.find(spCase => msg.includes(spCase))
|
||||
// special case for maths functions - don't have inputs yet
|
||||
return !!foundCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We have generated a static manifest file from the helpers that this string templating package makes use of.
|
||||
* This manifest provides information about each of the helpers and how it can be used.
|
||||
* @returns The manifest JSON which has been generated from the helpers.
|
||||
*/
|
||||
module.exports.getManifest = () => {
|
||||
return manifest
|
||||
}
|
||||
|
|
|
@ -2,23 +2,35 @@ const { FIND_HBS_REGEX } = require("../utilities")
|
|||
const preprocessor = require("./preprocessor")
|
||||
const postprocessor = require("./postprocessor")
|
||||
|
||||
function process(string, processors) {
|
||||
function process(output, processors) {
|
||||
for (let processor of processors) {
|
||||
// if a literal statement has occurred stop
|
||||
if (typeof output !== "string") {
|
||||
break
|
||||
}
|
||||
// re-run search each time incase previous processor updated/removed a match
|
||||
let regex = new RegExp(FIND_HBS_REGEX)
|
||||
let matches = string.match(regex)
|
||||
let regexp = new RegExp(FIND_HBS_REGEX)
|
||||
let matches = output.match(regexp)
|
||||
if (matches == null) {
|
||||
continue
|
||||
}
|
||||
for (let match of matches) {
|
||||
string = processor.process(string, match)
|
||||
output = processor.process(output, match)
|
||||
}
|
||||
}
|
||||
return string
|
||||
return output
|
||||
}
|
||||
|
||||
module.exports.preprocess = string => {
|
||||
return process(string, preprocessor.processors)
|
||||
module.exports.preprocess = (string, finalise = true) => {
|
||||
let processors = preprocessor.processors
|
||||
// the pre-processor finalisation stops handlebars from ever throwing an error
|
||||
// might want to pre-process for other benefits but still want to see errors
|
||||
if (!finalise) {
|
||||
processors = processors.filter(
|
||||
processor => processor.name !== preprocessor.PreprocessorNames.FINALISE
|
||||
)
|
||||
}
|
||||
return process(string, processors)
|
||||
}
|
||||
|
||||
module.exports.postprocess = string => {
|
||||
|
|
|
@ -1,9 +1,43 @@
|
|||
const { LITERAL_MARKER } = require("../helpers/constants")
|
||||
|
||||
const PostProcessorNames = {
|
||||
CONVERT_LITERALS: "convert-literals",
|
||||
}
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
class Postprocessor {
|
||||
constructor(name, fn) {
|
||||
this.name = name
|
||||
this.fn = fn
|
||||
}
|
||||
|
||||
process(statement) {
|
||||
return this.fn(statement)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.processors = []
|
||||
module.exports.processors = [
|
||||
new Postprocessor(PostProcessorNames.CONVERT_LITERALS, statement => {
|
||||
if (!statement.includes(LITERAL_MARKER)) {
|
||||
return statement
|
||||
}
|
||||
|
||||
const components = statement.split("-")
|
||||
// pop and shift remove the empty array elements from the first and last dash
|
||||
components.pop()
|
||||
components.shift()
|
||||
const type = components[1]
|
||||
const value = components[2]
|
||||
switch (type) {
|
||||
case "string":
|
||||
return value
|
||||
case "number":
|
||||
return parseFloat(value)
|
||||
case "boolean":
|
||||
return value === "true"
|
||||
case "object":
|
||||
return JSON.parse(value)
|
||||
}
|
||||
return value
|
||||
}),
|
||||
]
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
const { HelperNames } = require("../helpers")
|
||||
const { swapStrings, isAlphaNumeric } = require("../utilities")
|
||||
|
||||
const FUNCTION_CASES = ["#", "else", "/"]
|
||||
|
||||
const PreprocessorNames = {
|
||||
SWAP_TO_DOT: "swap-to-dot-notation",
|
||||
HANDLE_SPACES: "handle-spaces-in-properties",
|
||||
FIX_FUNCTIONS: "fix-functions",
|
||||
FINALISE: "finalise",
|
||||
}
|
||||
|
||||
|
@ -37,42 +39,16 @@ module.exports.processors = [
|
|||
return statement
|
||||
}),
|
||||
|
||||
new Preprocessor(PreprocessorNames.HANDLE_SPACES, statement => {
|
||||
// exclude helpers and brackets, regex will only find double brackets
|
||||
const exclusions = HelperNames()
|
||||
// find all the parts split by spaces
|
||||
const splitBySpaces = statement
|
||||
.split(" ")
|
||||
.filter(el => el !== "{{" && el !== "}}")
|
||||
// remove braces if they are found and weren't spaced out
|
||||
splitBySpaces[0] = splitBySpaces[0].replace("{", "")
|
||||
splitBySpaces[splitBySpaces.length - 1] = splitBySpaces[
|
||||
splitBySpaces.length - 1
|
||||
].replace("}", "")
|
||||
// remove the excluded elements
|
||||
const propertyParts = splitBySpaces.filter(
|
||||
part => exclusions.indexOf(part) === -1
|
||||
)
|
||||
// rebuild to get the full property
|
||||
const fullProperty = propertyParts.join(" ")
|
||||
// now work out the dot notation layers and split them up
|
||||
const propertyLayers = fullProperty.split(".")
|
||||
// find the layers which need to be wrapped and wrap them
|
||||
for (let layer of propertyLayers) {
|
||||
if (layer.indexOf(" ") !== -1) {
|
||||
statement = swapStrings(
|
||||
statement,
|
||||
statement.indexOf(layer),
|
||||
layer.length,
|
||||
`[${layer}]`
|
||||
)
|
||||
}
|
||||
new Preprocessor(PreprocessorNames.FIX_FUNCTIONS, statement => {
|
||||
for (let specialCase of FUNCTION_CASES) {
|
||||
const toFind = `{ ${specialCase}`,
|
||||
replacement = `{${specialCase}`
|
||||
statement = statement.replace(new RegExp(toFind, "g"), replacement)
|
||||
}
|
||||
// remove the edge case of double brackets being entered (in-case user already has specified)
|
||||
return statement.replace(/\[\[/g, "[").replace(/]]/g, "]")
|
||||
return statement
|
||||
}),
|
||||
|
||||
new Preprocessor(Preprocessor.FINALISE, statement => {
|
||||
new Preprocessor(PreprocessorNames.FINALISE, statement => {
|
||||
let insideStatement = statement.slice(2, statement.length - 2)
|
||||
if (insideStatement.charAt(0) === " ") {
|
||||
insideStatement = insideStatement.slice(1)
|
||||
|
@ -81,9 +57,17 @@ module.exports.processors = [
|
|||
insideStatement = insideStatement.slice(0, insideStatement.length - 1)
|
||||
}
|
||||
const possibleHelper = insideStatement.split(" ")[0]
|
||||
if (HelperNames().some(option => possibleHelper === option)) {
|
||||
// function helpers can't be wrapped
|
||||
for (let specialCase of FUNCTION_CASES) {
|
||||
if (possibleHelper.includes(specialCase)) {
|
||||
return statement
|
||||
}
|
||||
}
|
||||
if (HelperNames().some(option => possibleHelper.includes(option))) {
|
||||
insideStatement = `(${insideStatement})`
|
||||
}
|
||||
return `{{ all ${insideStatement} }}`
|
||||
}),
|
||||
]
|
||||
|
||||
module.exports.PreprocessorNames = PreprocessorNames
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const _ = require("lodash")
|
||||
const ALPHA_NUMERIC_REGEX = /^[A-Za-z0-9]+$/g
|
||||
|
||||
module.exports.FIND_HBS_REGEX = /{{([^{}])+}}/g
|
||||
module.exports.FIND_HBS_REGEX = /{{([^{].*?)}}/g
|
||||
|
||||
module.exports.isAlphaNumeric = char => {
|
||||
return char.match(ALPHA_NUMERIC_REGEX)
|
||||
|
@ -12,12 +13,22 @@ module.exports.swapStrings = (string, start, length, swap) => {
|
|||
|
||||
// removes null and undefined
|
||||
module.exports.removeNull = obj => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj)
|
||||
.filter(entry => entry[1] != null)
|
||||
.map(([key, value]) => [
|
||||
key,
|
||||
value === Object(value) ? module.exports.removeNull(value) : value,
|
||||
])
|
||||
)
|
||||
obj = _(obj)
|
||||
.omitBy(_.isUndefined)
|
||||
.omitBy(_.isNull)
|
||||
.value()
|
||||
for (let [key, value] of Object.entries(obj)) {
|
||||
// only objects
|
||||
if (typeof value === "object" && !Array.isArray(value)) {
|
||||
obj[key] = module.exports.removeNull(value)
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
module.exports.addConstants = obj => {
|
||||
if (obj.now == null) {
|
||||
obj.now = new Date()
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
const {
|
||||
processObject,
|
||||
processString,
|
||||
isValid,
|
||||
makePropSafe,
|
||||
getManifest,
|
||||
} = require("../src/index")
|
||||
|
||||
describe("Test that the string processing works correctly", () => {
|
||||
|
@ -81,4 +84,29 @@ describe("Test that the object processing works correctly", () => {
|
|||
}
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe("check the utility functions", () => {
|
||||
it("should return false for an invalid template string", () => {
|
||||
const valid = isValid("{{ table1.thing prop }}")
|
||||
expect(valid).toBe(false)
|
||||
})
|
||||
|
||||
it("should state this template is valid", () => {
|
||||
const valid = isValid("{{ thing }}")
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it("should make a property safe", () => {
|
||||
const property = makePropSafe("thing")
|
||||
expect(property).toEqual("[thing]")
|
||||
})
|
||||
})
|
||||
|
||||
describe("check manifest", () => {
|
||||
it("should be able to retrieve the manifest", () => {
|
||||
const manifest = getManifest()
|
||||
expect(manifest.math).not.toBeNull()
|
||||
expect(manifest.math.abs.description).toBe("<p>Return the magnitude of <code>a</code>.</p>\n")
|
||||
})
|
||||
})
|
|
@ -18,14 +18,14 @@ describe("Handling context properties with spaces in their name", () => {
|
|||
})
|
||||
|
||||
it("should be able to handle a property with a space in its name", async () => {
|
||||
const output = await processString("hello my name is {{ person name }}", {
|
||||
const output = await processString("hello my name is {{ [person name] }}", {
|
||||
"person name": "Mike",
|
||||
})
|
||||
expect(output).toBe("hello my name is Mike")
|
||||
})
|
||||
|
||||
it("should be able to handle an object with layers that requires escaping", async () => {
|
||||
const output = await processString("testcase {{ testing.test case }}", {
|
||||
const output = await processString("testcase {{ testing.[test case] }}", {
|
||||
testing: {
|
||||
"test case": 1
|
||||
}
|
||||
|
@ -44,8 +44,19 @@ describe("attempt some complex problems", () => {
|
|||
},
|
||||
},
|
||||
}
|
||||
const hbs = "{{ New Repeater.Get Actors.first_name }} {{ New Repeater.Get Actors.last_name }}"
|
||||
const hbs = "{{ [New Repeater].[Get Actors].[first_name] }} {{ [New Repeater].[Get Actors].[last_name] }}"
|
||||
const output = await processString(hbs, context)
|
||||
expect(output).toBe("Bob Bobert")
|
||||
})
|
||||
|
||||
it("should be able to process an odd string produced by builder", async () => {
|
||||
const context = {
|
||||
"c306d140d7e854f388bae056db380a0eb": {
|
||||
"test prop": "test",
|
||||
}
|
||||
}
|
||||
const hbs = "null{{ [c306d140d7e854f388bae056db380a0eb].[test prop] }}"
|
||||
const output = await processString(hbs, context)
|
||||
expect(output).toBe("nulltest")
|
||||
})
|
||||
})
|
|
@ -1,5 +1,6 @@
|
|||
const {
|
||||
processString,
|
||||
isValid,
|
||||
} = require("../src/index")
|
||||
|
||||
describe("test the custom helpers we have applied", () => {
|
||||
|
@ -9,5 +10,295 @@ describe("test the custom helpers we have applied", () => {
|
|||
})
|
||||
expect(output).toBe("object is {\"a\":1}")
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe("test the math helpers", () => {
|
||||
it("should be able to produce an absolute", async () => {
|
||||
const output = await processString("{{abs a}}", {
|
||||
a: -10,
|
||||
})
|
||||
expect(parseInt(output)).toBe(10)
|
||||
})
|
||||
|
||||
it("should be able to add", async () => {
|
||||
const output = await processString("{{add a b}}", {
|
||||
a: 10,
|
||||
b: 10,
|
||||
})
|
||||
expect(parseInt(output)).toBe(20)
|
||||
})
|
||||
|
||||
it("should be able to average", async () => {
|
||||
const output = await processString("{{avg a b c}}", {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
})
|
||||
expect(parseInt(output)).toBe(2)
|
||||
})
|
||||
|
||||
it("should be able to times", async () => {
|
||||
const output = await processString("{{times a b}}", {
|
||||
a: 5,
|
||||
b: 5,
|
||||
})
|
||||
expect(parseInt(output)).toBe(25)
|
||||
})
|
||||
})
|
||||
|
||||
describe("test the array helpers", () => {
|
||||
const array = ["hi", "person", "how", "are", "you"]
|
||||
it("should allow use of the after helper", async () => {
|
||||
const output = await processString("{{after array 1}}", {
|
||||
array,
|
||||
})
|
||||
expect(output).toBe("person,how,are,you")
|
||||
})
|
||||
|
||||
it("should allow use of the before helper", async () => {
|
||||
const output = await processString("{{before array 2}}", {
|
||||
array,
|
||||
})
|
||||
expect(output).toBe("hi,person,how")
|
||||
})
|
||||
|
||||
it("should allow use of the filter helper", async () => {
|
||||
const output = await processString("{{#filter array \"person\"}}THING{{else}}OTHER{{/filter}}", {
|
||||
array,
|
||||
})
|
||||
expect(output).toBe("THING")
|
||||
})
|
||||
|
||||
it("should allow use of the itemAt helper", async () => {
|
||||
const output = await processString("{{itemAt array 1}}", {
|
||||
array,
|
||||
})
|
||||
expect(output).toBe("person")
|
||||
})
|
||||
|
||||
it("should allow use of the join helper", async () => {
|
||||
const output = await processString("{{join array \"-\"}}", {
|
||||
array,
|
||||
})
|
||||
expect(output).toBe("hi-person-how-are-you")
|
||||
})
|
||||
|
||||
it("should allow use of the sort helper", async () => {
|
||||
const output = await processString("{{sort array}}", {
|
||||
array: ["d", "a", "c", "e"]
|
||||
})
|
||||
expect(output).toBe("a,c,d,e")
|
||||
})
|
||||
|
||||
it("should allow use of the unique helper", async () => {
|
||||
const output = await processString("{{unique array}}", {
|
||||
array: ["a", "a", "b"]
|
||||
})
|
||||
expect(output).toBe("a,b")
|
||||
})
|
||||
})
|
||||
|
||||
describe("test the number helpers", () => {
|
||||
it("should allow use of the addCommas helper", async () => {
|
||||
const output = await processString("{{ addCommas number }}", {
|
||||
number: 10000000
|
||||
})
|
||||
expect(output).toBe("10,000,000")
|
||||
})
|
||||
|
||||
it("should allow use of the phoneNumber helper", async () => {
|
||||
const output = await processString("{{ phoneNumber number }}", {
|
||||
number: 4490102030,
|
||||
})
|
||||
expect(output).toBe("(449) 010-2030")
|
||||
})
|
||||
|
||||
it("should allow use of the toPrecision helper", async () => {
|
||||
const output = await processString("{{ toPrecision number 2 }}", {
|
||||
number: 1.222222222,
|
||||
})
|
||||
expect(output).toBe("1.2")
|
||||
})
|
||||
|
||||
it("should allow use of the bytes helper", async () => {
|
||||
const output = await processString("{{ bytes number }}", {
|
||||
number: 1000000,
|
||||
})
|
||||
expect(output).toBe("1 MB")
|
||||
})
|
||||
})
|
||||
|
||||
describe("test the url helpers", () => {
|
||||
const url = "http://example.com?query=1"
|
||||
it("should allow use of the stripQueryString helper", async () => {
|
||||
const output = await processString('{{stripQuerystring url }}', {
|
||||
url,
|
||||
})
|
||||
expect(output).toBe("http://example.com")
|
||||
})
|
||||
|
||||
it("should allow use of the stripProtocol helper", async () => {
|
||||
const output = await processString("{{ stripProtocol url }}", {
|
||||
url,
|
||||
})
|
||||
expect(output).toBe("//example.com/?query=1")
|
||||
})
|
||||
|
||||
it("should allow use of the urlParse helper", async () => {
|
||||
const output = await processString("{{ object ( urlParse url ) }}", {
|
||||
url,
|
||||
})
|
||||
expect(output).toBe("{\"protocol\":\"http:\",\"slashes\":true,\"auth\":null,\"host\":\"example.com\"," +
|
||||
"\"port\":null,\"hostname\":\"example.com\",\"hash\":null,\"search\":\"?query=1\"," +
|
||||
"\"query\":\"query=1\",\"pathname\":\"/\",\"path\":\"/?query=1\"," +
|
||||
"\"href\":\"http://example.com/?query=1\"}")
|
||||
})
|
||||
})
|
||||
|
||||
describe("test the date helpers", () => {
|
||||
it("should allow use of the date helper", async () => {
|
||||
const date = new Date(1611577535000)
|
||||
const output = await processString("{{ date time 'YYYY-MM-DD' }}", {
|
||||
time: date.toISOString(),
|
||||
})
|
||||
expect(output).toBe("2021-01-25")
|
||||
})
|
||||
|
||||
it("should allow use of the date helper with now time", async () => {
|
||||
const date = new Date()
|
||||
const output = await processString("{{ date now 'DD' }}", {})
|
||||
expect(parseInt(output)).toBe(date.getDate())
|
||||
})
|
||||
})
|
||||
|
||||
describe("test the string helpers", () => {
|
||||
it("should allow use of the append helper", async () => {
|
||||
const output = await processString("{{ append filename '.txt' }}", {
|
||||
filename: "yummy",
|
||||
})
|
||||
expect(output).toBe("yummy.txt")
|
||||
})
|
||||
|
||||
it("should allow use of the camelcase helper", async () => {
|
||||
const output = await processString("{{ camelcase camel }}", {
|
||||
camel: "testing this thing",
|
||||
})
|
||||
expect(output).toBe("testingThisThing")
|
||||
})
|
||||
|
||||
it("should allow use of the capitalize helper", async () => {
|
||||
const output = await processString("{{ capitalize string }}", {
|
||||
string: "this is a string",
|
||||
})
|
||||
expect(output).toBe("This is a string")
|
||||
})
|
||||
|
||||
it("should allow use of the capitalizeAll helper", async () => {
|
||||
const output = await processString("{{ capitalizeAll string }}", {
|
||||
string: "this is a string",
|
||||
})
|
||||
expect(output).toBe("This Is A String")
|
||||
})
|
||||
|
||||
it("should allow use of the replace helper", async () => {
|
||||
const output = await processString("{{ replace string 'Mike' name }}", {
|
||||
string: "Hello my name is Mike",
|
||||
name: "David",
|
||||
})
|
||||
expect(output).toBe("Hello my name is David")
|
||||
})
|
||||
|
||||
it("should allow use of the split helper", async () => {
|
||||
const output = await processString("{{ first ( split string ' ' ) }}", {
|
||||
string: "this is a string",
|
||||
})
|
||||
expect(output).toBe("this")
|
||||
})
|
||||
|
||||
it("should allow use of the remove helper", async () => {
|
||||
const output = await processString("{{ remove string 'string' }}", {
|
||||
string: "this is a string",
|
||||
})
|
||||
expect(output).toBe("this is a ")
|
||||
})
|
||||
|
||||
it("should allow use of the startsWith helper", async () => {
|
||||
const output = await processString("{{ #startsWith 'Hello' string }}Hi!{{ else }}Goodbye!{{ /startsWith }}", {
|
||||
string: "Hello my name is Mike",
|
||||
})
|
||||
expect(output).toBe("Hi!")
|
||||
})
|
||||
})
|
||||
|
||||
describe("test the comparison helpers", () => {
|
||||
async function compare(func, a, b) {
|
||||
const output = await processString(`{{ #${func} a b }}Success{{ else }}Fail{{ /${func} }}`, {
|
||||
a,
|
||||
b,
|
||||
})
|
||||
expect(output).toBe("Success")
|
||||
}
|
||||
it("should allow use of the lt helper", async () => {
|
||||
await compare("lt", 10, 15)
|
||||
})
|
||||
|
||||
it("should allow use of the gt helper", async () => {
|
||||
await compare("gt", 15, 10)
|
||||
})
|
||||
|
||||
it("should allow use of the and helper", async () => {
|
||||
await compare("and", true, true)
|
||||
})
|
||||
|
||||
it("should allow use of the or helper", async () => {
|
||||
await compare("or", false, true)
|
||||
})
|
||||
|
||||
it("should allow use of gte with a literal value", async () => {
|
||||
const output = await processString(`{{ #gte a "50" }}s{{ else }}f{{ /gte }}`, {
|
||||
a: 51,
|
||||
})
|
||||
expect(output).toBe("s")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Test the literal helper", () => {
|
||||
it("should allow use of the literal specifier for a number", async () => {
|
||||
const output = await processString(`{{literal a}}`, {
|
||||
a: 51,
|
||||
})
|
||||
expect(output).toBe(51)
|
||||
})
|
||||
|
||||
it("should allow use of the literal specifier for an object", async () => {
|
||||
const output = await processString(`{{literal a}}`, {
|
||||
a: {b: 1},
|
||||
})
|
||||
expect(output.b).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Cover a few complex use cases", () => {
|
||||
it("should allow use of three different collection helpers", async () => {
|
||||
const output = await processString(`{{ join ( after ( split "My name is: Joe Smith" " " ) 3 ) " " }}`, {})
|
||||
expect(output).toBe("Joe Smith")
|
||||
})
|
||||
|
||||
it("should allow a complex array case", async () => {
|
||||
const output = await processString("{{ last ( sort ( unique array ) ) }}", {
|
||||
array: ["a", "a", "d", "c", "e"]
|
||||
})
|
||||
expect(output).toBe("e")
|
||||
})
|
||||
|
||||
it("should make sure case is valid", () => {
|
||||
const validity = isValid("{{ avg [c355ec2b422e54f988ae553c8acd811ea].[a] [c355ec2b422e54f988ae553c8acd811ea].[b] }}")
|
||||
expect(validity).toBe(true)
|
||||
})
|
||||
|
||||
it("should be able to solve an example from docs", async () => {
|
||||
const output = await processString(`{{first ( split "a-b-c" "-") 2}}`, {})
|
||||
expect(output).toBe(`a,b`)
|
||||
|
||||
})
|
||||
})
|
|
@ -270,6 +270,38 @@
|
|||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||
|
||||
"@budibase/handlebars-helpers@^0.11.1":
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.1.tgz#fe8672612fb4ad8fd3bad338ee15759f45e421eb"
|
||||
integrity sha512-s72LhOhlHk43QYGVcqjogaGa4h4e6y6X1AfshqWBWceTRYBs/9tUnoYDUiRJ/Mt3WoTmx8Hj5GOZekSqrUsOFQ==
|
||||
dependencies:
|
||||
arr-flatten "^1.1.0"
|
||||
array-sort "^0.1.4"
|
||||
define-property "^1.0.0"
|
||||
extend-shallow "^3.0.2"
|
||||
"falsey" "^0.3.2"
|
||||
for-in "^1.0.2"
|
||||
for-own "^1.0.0"
|
||||
get-object "^0.2.0"
|
||||
get-value "^2.0.6"
|
||||
handlebars "^4.0.11"
|
||||
handlebars-utils "^1.0.6"
|
||||
has-value "^1.0.0"
|
||||
helper-date "^1.0.1"
|
||||
helper-markdown "^1.0.0"
|
||||
helper-md "^0.2.2"
|
||||
html-tag "^2.0.0"
|
||||
is-even "^1.0.0"
|
||||
is-glob "^4.0.0"
|
||||
is-number "^4.0.0"
|
||||
kind-of "^6.0.0"
|
||||
logging-helpers "^1.0.0"
|
||||
micromatch "^3.1.4"
|
||||
relative "^3.0.2"
|
||||
striptags "^3.1.0"
|
||||
to-gfm-code-block "^0.1.1"
|
||||
year "^0.2.1"
|
||||
|
||||
"@cnakazawa/watch@^1.0.3":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
|
||||
|
@ -465,6 +497,19 @@
|
|||
"@types/yargs" "^15.0.0"
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@rollup/plugin-commonjs@^17.1.0":
|
||||
version "17.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz#757ec88737dffa8aa913eb392fade2e45aef2a2d"
|
||||
integrity sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew==
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^3.1.0"
|
||||
commondir "^1.0.1"
|
||||
estree-walker "^2.0.1"
|
||||
glob "^7.1.6"
|
||||
is-reference "^1.2.1"
|
||||
magic-string "^0.25.7"
|
||||
resolve "^1.17.0"
|
||||
|
||||
"@rollup/plugin-json@^4.1.0":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3"
|
||||
|
@ -472,7 +517,7 @@
|
|||
dependencies:
|
||||
"@rollup/pluginutils" "^3.0.8"
|
||||
|
||||
"@rollup/pluginutils@^3.0.8":
|
||||
"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
|
||||
integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
|
||||
|
@ -1389,6 +1434,16 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
|
|||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commondir@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
||||
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
|
||||
|
||||
component-emitter@^1.2.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
||||
|
@ -1441,16 +1496,6 @@ create-ecdh@^4.0.0:
|
|||
bn.js "^4.1.0"
|
||||
elliptic "^6.5.3"
|
||||
|
||||
create-frame@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/create-frame/-/create-frame-1.0.0.tgz#8b95f2691e3249b6080443e33d0bad9f8f6975aa"
|
||||
integrity sha1-i5XyaR4ySbYIBEPjPQutn49pdao=
|
||||
dependencies:
|
||||
define-property "^0.2.5"
|
||||
extend-shallow "^2.0.1"
|
||||
isobject "^3.0.0"
|
||||
lazy-cache "^2.0.2"
|
||||
|
||||
create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
|
||||
|
@ -1665,6 +1710,13 @@ diffie-hellman@^5.0.0:
|
|||
miller-rabin "^4.0.0"
|
||||
randombytes "^2.0.0"
|
||||
|
||||
doctrine@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
|
||||
integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
domexception@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304"
|
||||
|
@ -1781,6 +1833,11 @@ estree-walker@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
|
||||
integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
|
||||
|
||||
estree-walker@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
||||
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||
|
||||
esutils@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||
|
@ -2074,7 +2131,7 @@ getpass@^0.1.1:
|
|||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
|
||||
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||
version "7.1.6"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
||||
|
@ -2110,48 +2167,6 @@ gulp-header@^1.7.1:
|
|||
lodash.template "^4.4.0"
|
||||
through2 "^2.0.0"
|
||||
|
||||
handlebars-helper-create-frame@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/handlebars-helper-create-frame/-/handlebars-helper-create-frame-0.1.0.tgz#8aa51d10aeb6408fcc6605d40d77356288487a03"
|
||||
integrity sha1-iqUdEK62QI/MZgXUDXc1YohIegM=
|
||||
dependencies:
|
||||
create-frame "^1.0.0"
|
||||
isobject "^3.0.0"
|
||||
|
||||
handlebars-helpers@^0.10.0:
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/handlebars-helpers/-/handlebars-helpers-0.10.0.tgz#663d49e718928eafbead1473419ed7bc24bcd45a"
|
||||
integrity sha512-QiyhQz58u/DbuV41VnfpE0nhy6YCH4vB514ajysV8SoKmP+DxU+pR+fahVyNECHj+jiwEN2VrvxD/34/yHaLUg==
|
||||
dependencies:
|
||||
arr-flatten "^1.1.0"
|
||||
array-sort "^0.1.4"
|
||||
create-frame "^1.0.0"
|
||||
define-property "^1.0.0"
|
||||
"falsey" "^0.3.2"
|
||||
for-in "^1.0.2"
|
||||
for-own "^1.0.0"
|
||||
get-object "^0.2.0"
|
||||
get-value "^2.0.6"
|
||||
handlebars "^4.0.11"
|
||||
handlebars-helper-create-frame "^0.1.0"
|
||||
handlebars-utils "^1.0.6"
|
||||
has-value "^1.0.0"
|
||||
helper-date "^1.0.1"
|
||||
helper-markdown "^1.0.0"
|
||||
helper-md "^0.2.2"
|
||||
html-tag "^2.0.0"
|
||||
is-even "^1.0.0"
|
||||
is-glob "^4.0.0"
|
||||
is-number "^4.0.0"
|
||||
kind-of "^6.0.0"
|
||||
lazy-cache "^2.0.2"
|
||||
logging-helpers "^1.0.0"
|
||||
micromatch "^3.1.4"
|
||||
relative "^3.0.2"
|
||||
striptags "^3.1.0"
|
||||
to-gfm-code-block "^0.1.1"
|
||||
year "^0.2.1"
|
||||
|
||||
handlebars-utils@^1.0.2, handlebars-utils@^1.0.4, handlebars-utils@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/handlebars-utils/-/handlebars-utils-1.0.6.tgz#cb9db43362479054782d86ffe10f47abc76357f9"
|
||||
|
@ -2553,7 +2568,7 @@ is-potential-custom-element-name@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397"
|
||||
integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c=
|
||||
|
||||
is-reference@^1.1.2:
|
||||
is-reference@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7"
|
||||
integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==
|
||||
|
@ -3037,7 +3052,7 @@ jest-watcher@^26.6.2:
|
|||
jest-util "^26.6.2"
|
||||
string-length "^4.0.1"
|
||||
|
||||
jest-worker@^26.6.2:
|
||||
jest-worker@^26.2.1, jest-worker@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
|
||||
integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==
|
||||
|
@ -3176,7 +3191,7 @@ kleur@^3.0.3:
|
|||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
||||
|
||||
lazy-cache@^2.0.1, lazy-cache@^2.0.2:
|
||||
lazy-cache@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264"
|
||||
integrity sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=
|
||||
|
@ -3371,7 +3386,7 @@ magic-string@^0.22.5:
|
|||
dependencies:
|
||||
vlq "^0.2.2"
|
||||
|
||||
magic-string@^0.25.2:
|
||||
magic-string@^0.25.7:
|
||||
version "0.25.7"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
|
||||
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
|
||||
|
@ -3404,6 +3419,11 @@ map-visit@^1.0.0:
|
|||
dependencies:
|
||||
object-visit "^1.0.0"
|
||||
|
||||
marked@^1.2.8:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.8.tgz#5008ece15cfa43e653e85845f3525af4beb6bdd4"
|
||||
integrity sha512-lzmFjGnzWHkmbk85q/ILZjFoHHJIQGF+SxGEfIdGk/XhiTPhqGs37gbru6Kkd48diJnEyYwnG67nru0Z2gQtuQ==
|
||||
|
||||
md5.js@^1.3.4:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
|
||||
|
@ -3886,7 +3906,7 @@ qs@~6.5.2:
|
|||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
||||
|
||||
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
|
||||
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
|
||||
|
@ -4074,7 +4094,7 @@ resolve-url@^0.2.1:
|
|||
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
|
||||
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
|
||||
|
||||
resolve@^1.10.0, resolve@^1.11.0, resolve@^1.11.1, resolve@^1.18.1:
|
||||
resolve@^1.10.0, resolve@^1.11.1, resolve@^1.17.0, resolve@^1.18.1:
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c"
|
||||
integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==
|
||||
|
@ -4102,17 +4122,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
|
|||
hash-base "^3.0.0"
|
||||
inherits "^2.0.1"
|
||||
|
||||
rollup-plugin-commonjs@^10.1.0:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz#417af3b54503878e084d127adf4d1caf8beb86fb"
|
||||
integrity sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==
|
||||
dependencies:
|
||||
estree-walker "^0.6.1"
|
||||
is-reference "^1.1.2"
|
||||
magic-string "^0.25.2"
|
||||
resolve "^1.11.0"
|
||||
rollup-pluginutils "^2.8.1"
|
||||
|
||||
rollup-plugin-node-builtins@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-node-builtins/-/rollup-plugin-node-builtins-2.1.2.tgz#24a1fed4a43257b6b64371d8abc6ce1ab14597e9"
|
||||
|
@ -4146,6 +4155,16 @@ rollup-plugin-node-resolve@^5.2.0:
|
|||
resolve "^1.11.1"
|
||||
rollup-pluginutils "^2.8.1"
|
||||
|
||||
rollup-plugin-terser@^7.0.2:
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
|
||||
integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.10.4"
|
||||
jest-worker "^26.2.1"
|
||||
serialize-javascript "^4.0.0"
|
||||
terser "^5.0.0"
|
||||
|
||||
rollup-pluginutils@^2.3.1, rollup-pluginutils@^2.8.1:
|
||||
version "2.8.2"
|
||||
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"
|
||||
|
@ -4236,6 +4255,13 @@ semver@~2.3.1:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-2.3.2.tgz#b9848f25d6cf36333073ec9ef8856d42f1233e52"
|
||||
integrity sha1-uYSPJdbPNjMwc+ye+IVtQvEjPlI=
|
||||
|
||||
serialize-javascript@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
|
||||
integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
set-blocking@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
|
@ -4351,7 +4377,7 @@ source-map-resolve@^0.5.0:
|
|||
source-map-url "^0.4.0"
|
||||
urix "^0.1.0"
|
||||
|
||||
source-map-support@^0.5.6:
|
||||
source-map-support@^0.5.6, source-map-support@~0.5.19:
|
||||
version "0.5.19"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
|
||||
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
|
||||
|
@ -4374,7 +4400,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
|
|||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@^0.7.3:
|
||||
source-map@^0.7.3, source-map@~0.7.2:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||
|
@ -4565,6 +4591,15 @@ terminal-link@^2.0.0:
|
|||
ansi-escapes "^4.2.1"
|
||||
supports-hyperlinks "^2.0.0"
|
||||
|
||||
terser@^5.0.0:
|
||||
version "5.5.1"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.1.tgz#540caa25139d6f496fdea056e414284886fb2289"
|
||||
integrity sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==
|
||||
dependencies:
|
||||
commander "^2.20.0"
|
||||
source-map "~0.7.2"
|
||||
source-map-support "~0.5.19"
|
||||
|
||||
test-exclude@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
|
||||
|
@ -4732,9 +4767,9 @@ typescript@^4.1.3:
|
|||
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.12.4"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.12.4.tgz#93de48bb76bb3ec0fc36563f871ba46e2ee5c7ee"
|
||||
integrity sha512-L5i5jg/SHkEqzN18gQMTWsZk3KelRsfD1wUVNqtq0kzqWQqcJjyL8yc1o8hJgRrWqrAl2mUFbhfznEIoi7zi2A==
|
||||
version "3.12.6"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.12.6.tgz#f884584fcc42e10bca70db5cb32e8625c2c42535"
|
||||
integrity sha512-aqWHe3DfQmZUDGWBbabZ2eQnJlQd1fKlMUu7gV+MiTuDzdgDw31bI3wA2jLLsV/hNcDP26IfyEgSVoft5+0SVw==
|
||||
|
||||
union-value@^1.0.0:
|
||||
version "1.0.1"
|
||||
|
|
Loading…
Reference in New Issue