Merge pull request #1020 from Budibase/feature/handlebars-helpers
Feature/handlebars helpers
This commit is contained in:
commit
eea86fca83
|
@ -2,6 +2,7 @@ import { cloneDeep } from "lodash/fp"
|
|||
import { get } from "svelte/store"
|
||||
import { backendUiStore, store } from "builderStore"
|
||||
import { findAllMatchingComponents, findComponentPath } from "./storeUtils"
|
||||
import { makePropSafe } from "@budibase/string-templates"
|
||||
import { TableNames } from "../constants"
|
||||
|
||||
// Regex to match all instances of template strings
|
||||
|
@ -106,7 +107,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,
|
||||
|
@ -167,7 +170,7 @@ export const getComponentBindings = rootComponent => {
|
|||
return {
|
||||
type: "instance",
|
||||
providerId: component._id,
|
||||
runtimeBinding: `${component._id}`,
|
||||
runtimeBinding: `${makePropSafe(component._id)}`,
|
||||
readableBinding: `${component._instanceName}`,
|
||||
}
|
||||
})
|
||||
|
@ -199,43 +202,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 {
|
||||
makeMainContainer,
|
||||
makeBreadcrumbContainer,
|
||||
|
@ -12,7 +13,7 @@ import {
|
|||
export default function(tables) {
|
||||
return tables.map(table => {
|
||||
const heading = table.primaryDisplay
|
||||
? `{{ data.${table.primaryDisplay} }}`
|
||||
? `{{ data.${makePropSafe(table.primaryDisplay)} }}`
|
||||
: null
|
||||
return {
|
||||
name: `${table.name} - Detail`,
|
||||
|
@ -60,8 +61,8 @@ function generateTitleContainer(table, title, providerId) {
|
|||
onClick: [
|
||||
{
|
||||
parameters: {
|
||||
rowId: `{{ ${providerId}._id }}`,
|
||||
revId: `{{ ${providerId}._rev }}`,
|
||||
rowId: `{{ ${makePropSafe(providerId)}._id }}`,
|
||||
revId: `{{ ${makePropSafe(providerId)}._rev }}`,
|
||||
tableId: table._id,
|
||||
},
|
||||
"##eventHandlerType": "Delete Row",
|
||||
|
|
|
@ -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,
|
||||
|
@ -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)}
|
||||
|
|
|
@ -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,
|
||||
}))
|
||||
)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
|
|
|
@ -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.
|
File diff suppressed because it is too large
Load Diff
|
@ -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,152 @@
|
|||
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 example = docs.description.split("```")
|
||||
if (example.length > 1) {
|
||||
docs.example = example[1]
|
||||
}
|
||||
docs.description = example[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