Merge branch 'feature/handlebars-migration' of github.com:Budibase/budibase into form-builder
This commit is contained in:
commit
4cdca6f196
|
@ -11,9 +11,7 @@
|
||||||
on:click
|
on:click
|
||||||
class:big={subtitle != null}
|
class:big={subtitle != null}
|
||||||
{...$$restProps}>
|
{...$$restProps}>
|
||||||
{#if icon}
|
{#if icon}<i class={icon} />{/if}
|
||||||
<i class={icon} />
|
|
||||||
{/if}
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="title">{title}</div>
|
<div class="title">{title}</div>
|
||||||
{#if subtitle != null}
|
{#if subtitle != null}
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import groupBy from "lodash/fp/groupBy"
|
import groupBy from "lodash/fp/groupBy"
|
||||||
import {
|
import { Button, TextArea, Drawer, Heading, Spacer } from "@budibase/bbui"
|
||||||
Button,
|
|
||||||
TextArea,
|
|
||||||
Drawer,
|
|
||||||
Heading,
|
|
||||||
Spacer,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -38,7 +32,9 @@
|
||||||
<Heading extraSmall>Tables</Heading>
|
<Heading extraSmall>Tables</Heading>
|
||||||
<ul>
|
<ul>
|
||||||
{#each context as { readableBinding }}
|
{#each context as { readableBinding }}
|
||||||
<li on:click={() => addToText(readableBinding)}>{readableBinding}</li>
|
<li on:click={() => addToText(readableBinding)}>
|
||||||
|
{readableBinding}
|
||||||
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -46,7 +42,9 @@
|
||||||
<Heading extraSmall>Components</Heading>
|
<Heading extraSmall>Components</Heading>
|
||||||
<ul>
|
<ul>
|
||||||
{#each instance as { readableBinding }}
|
{#each instance as { readableBinding }}
|
||||||
<li on:click={() => addToText(readableBinding)}>{readableBinding}</li>
|
<li on:click={() => addToText(readableBinding)}>
|
||||||
|
{readableBinding}
|
||||||
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
import iconData from "./icons.js"
|
import iconData from "./icons.js"
|
||||||
|
|
||||||
const categories = Object.keys(iconData)
|
const categories = Object.keys(iconData)
|
||||||
const icons = Object.keys(iconData).reduce((acc, cat) => [...acc, ...Object.keys(iconData[cat])], [])
|
const icons = Object.keys(iconData).reduce(
|
||||||
|
(acc, cat) => [...acc, ...Object.keys(iconData[cat])],
|
||||||
|
[]
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -177,6 +177,7 @@ exports.serveApp = async function(ctx) {
|
||||||
})
|
})
|
||||||
|
|
||||||
const appHbs = fs.readFileSync(`${__dirname}/templates/app.hbs`, "utf8")
|
const appHbs = fs.readFileSync(`${__dirname}/templates/app.hbs`, "utf8")
|
||||||
|
console.log(appHbs)
|
||||||
ctx.body = await processString(appHbs, {
|
ctx.body = await processString(appHbs, {
|
||||||
head,
|
head,
|
||||||
body: html,
|
body: html,
|
||||||
|
|
|
@ -22,7 +22,9 @@
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<link rel="icon" type="image/png" href={favicon} />
|
<link rel="icon" type="image/png" href={favicon} />
|
||||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
||||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css" rel="stylesheet">
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css"
|
||||||
|
rel="stylesheet" />
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
|
|
|
@ -10,6 +10,4 @@
|
||||||
export let color = "#000"
|
export let color = "#000"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<i
|
<i style={`color: ${color};`} class={`${icon} ${size}`} />
|
||||||
style={`color: ${color};`}
|
|
||||||
class={`${icon} ${size}`} />
|
|
||||||
|
|
|
@ -191,4 +191,4 @@ module.exports = {
|
||||||
|
|
||||||
// Whether to use watchman for file crawling
|
// Whether to use watchman for file crawling
|
||||||
// watchman: true,
|
// watchman: true,
|
||||||
};
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default {
|
||||||
name: "string-templates",
|
name: "string-templates",
|
||||||
exports: "named",
|
exports: "named",
|
||||||
globals: {
|
globals: {
|
||||||
"fs": "fs",
|
fs: "fs",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
external: ["fs"],
|
external: ["fs"],
|
||||||
|
|
|
@ -1,60 +1,81 @@
|
||||||
const { HelperFunctions } = require("../helpers")
|
const { HelperFunctions } = require("../helpers")
|
||||||
const { swapStrings, isAlphaNumeric, FIND_HBS_REGEX, includesAny } = require("../utilities")
|
const {
|
||||||
|
swapStrings,
|
||||||
|
isAlphaNumeric,
|
||||||
|
FIND_HBS_REGEX,
|
||||||
|
includesAny,
|
||||||
|
} = require("../utilities")
|
||||||
|
|
||||||
function handleProcessor(string, match, fn) {
|
class Preprocessor {
|
||||||
const output = fn(match)
|
constructor(name, fn) {
|
||||||
const idx = string.indexOf(match)
|
this.name = name
|
||||||
return swapStrings(string, idx, match.length, output)
|
this.fn = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
process(fullString, statement) {
|
||||||
|
const output = this.fn(statement)
|
||||||
|
const idx = fullString.indexOf(statement)
|
||||||
|
return swapStrings(fullString, idx, statement.length, output)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapToDotNotation(statement) {
|
const PROCESSORS = [
|
||||||
let startBraceIdx = statement.indexOf("[")
|
new Preprocessor("swap-to-dot-notation", statement => {
|
||||||
let lastIdx = 0
|
let startBraceIdx = statement.indexOf("[")
|
||||||
while (startBraceIdx !== -1) {
|
let lastIdx = 0
|
||||||
// if the character previous to the literal specifier is alpha-numeric this should happen
|
while (startBraceIdx !== -1) {
|
||||||
if (isAlphaNumeric(statement.charAt(startBraceIdx - 1))) {
|
// if the character previous to the literal specifier is alpha-numeric this should happen
|
||||||
statement = swapStrings(statement, startBraceIdx + lastIdx, 1, ".[")
|
if (isAlphaNumeric(statement.charAt(startBraceIdx - 1))) {
|
||||||
|
statement = swapStrings(statement, startBraceIdx + lastIdx, 1, ".[")
|
||||||
|
}
|
||||||
|
lastIdx = startBraceIdx + 1
|
||||||
|
startBraceIdx = statement.substring(lastIdx + 1).indexOf("[")
|
||||||
}
|
}
|
||||||
lastIdx = startBraceIdx + 1
|
return statement
|
||||||
startBraceIdx = statement.substring(lastIdx + 1).indexOf("[")
|
}),
|
||||||
}
|
|
||||||
return statement
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSpacesInProperties(statement) {
|
new Preprocessor("handle-spaces-in-properties", statement => {
|
||||||
// exclude helpers and brackets, regex will only find double brackets
|
// exclude helpers and brackets, regex will only find double brackets
|
||||||
const exclusions = HelperFunctions.concat(["{{", "}}"])
|
const exclusions = HelperFunctions.concat(["{{", "}}"])
|
||||||
// find all the parts split by spaces
|
// find all the parts split by spaces
|
||||||
const splitBySpaces = statement.split(" ")
|
const splitBySpaces = statement.split(" ")
|
||||||
// remove the excluded elements
|
// remove the excluded elements
|
||||||
const propertyParts = splitBySpaces.filter(part => exclusions.indexOf(part) === -1)
|
const propertyParts = splitBySpaces.filter(
|
||||||
// rebuild to get the full property
|
part => exclusions.indexOf(part) === -1
|
||||||
const fullProperty = propertyParts.join(" ")
|
)
|
||||||
// now work out the dot notation layers and split them up
|
// rebuild to get the full property
|
||||||
const propertyLayers = fullProperty.split(".")
|
const fullProperty = propertyParts.join(" ")
|
||||||
// find the layers which need to be wrapped and wrap them
|
// now work out the dot notation layers and split them up
|
||||||
for (let layer of propertyLayers) {
|
const propertyLayers = fullProperty.split(".")
|
||||||
if (layer.indexOf(" ") !== -1) {
|
// find the layers which need to be wrapped and wrap them
|
||||||
statement = swapStrings(statement, statement.indexOf(layer), layer.length, `[${layer}]`)
|
for (let layer of propertyLayers) {
|
||||||
|
if (layer.indexOf(" ") !== -1) {
|
||||||
|
statement = swapStrings(
|
||||||
|
statement,
|
||||||
|
statement.indexOf(layer),
|
||||||
|
layer.length,
|
||||||
|
`[${layer}]`
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
// remove the edge case of double brackets being entered (in-case user already has specified)
|
||||||
// remove the edge case of double brackets being entered (in-case user already has specified)
|
return statement.replace(/\[\[/g, "[").replace(/]]/g, "]")
|
||||||
return statement.replace(/\[\[/g, "[").replace(/]]/g, "]")
|
}),
|
||||||
}
|
|
||||||
|
|
||||||
function finalise(statement) {
|
new Preprocessor("finalise", statement => {
|
||||||
let insideStatement = statement.slice(2, statement.length - 2)
|
let insideStatement = statement.slice(2, statement.length - 2)
|
||||||
if (insideStatement.charAt(0) === " ") {
|
if (insideStatement.charAt(0) === " ") {
|
||||||
insideStatement = insideStatement.slice(1)
|
insideStatement = insideStatement.slice(1)
|
||||||
}
|
}
|
||||||
if (insideStatement.charAt(insideStatement.length - 1) === " ") {
|
if (insideStatement.charAt(insideStatement.length - 1) === " ") {
|
||||||
insideStatement = insideStatement.slice(0, insideStatement.length - 1)
|
insideStatement = insideStatement.slice(0, insideStatement.length - 1)
|
||||||
}
|
}
|
||||||
if (includesAny(insideStatement, HelperFunctions)) {
|
if (includesAny(insideStatement, HelperFunctions)) {
|
||||||
insideStatement = `(${insideStatement})`
|
insideStatement = `(${insideStatement})`
|
||||||
}
|
}
|
||||||
return `{{ all ${insideStatement} }}`
|
return `{{ all ${insideStatement} }}`
|
||||||
}
|
})
|
||||||
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When running handlebars statements to execute on the context of the automation it possible user's may input handlebars
|
* When running handlebars statements to execute on the context of the automation it possible user's may input handlebars
|
||||||
|
@ -67,17 +88,16 @@ function finalise(statement) {
|
||||||
* @param {string} string The string which *may* contain handlebars statements, it is OK if it does not contain any.
|
* @param {string} string The string which *may* contain handlebars statements, it is OK if it does not contain any.
|
||||||
* @returns {string} The string that was input with processed up handlebars statements as required.
|
* @returns {string} The string that was input with processed up handlebars statements as required.
|
||||||
*/
|
*/
|
||||||
module.exports.preprocess = (string) => {
|
module.exports.preprocess = string => {
|
||||||
let preprocessors = [swapToDotNotation, handleSpacesInProperties, finalise]
|
for (let processor of PROCESSORS) {
|
||||||
for (let processor of preprocessors) {
|
// re-run search each time incase previous processor updated/removed a match
|
||||||
// re-run search each time incase previous cleaner update/removed a match
|
|
||||||
let regex = new RegExp(FIND_HBS_REGEX)
|
let regex = new RegExp(FIND_HBS_REGEX)
|
||||||
let matches = string.match(regex)
|
let matches = string.match(regex)
|
||||||
if (matches == null) {
|
if (matches == null) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for (let match of matches) {
|
for (let match of matches) {
|
||||||
string = handleProcessor(string, match, processor)
|
string = processor.process(string, match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return string
|
return string
|
||||||
|
|
|
@ -12,7 +12,7 @@ const HelperFunctionBuiltin = [
|
||||||
"#each",
|
"#each",
|
||||||
"#with",
|
"#with",
|
||||||
"lookup",
|
"lookup",
|
||||||
"log"
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
const HelperFunctionNames = {
|
const HelperFunctionNames = {
|
||||||
|
@ -27,17 +27,19 @@ const HELPERS = [
|
||||||
}),
|
}),
|
||||||
// this help is applied to all statements
|
// this help is applied to all statements
|
||||||
new Helper(HelperFunctionNames.ALL, value => {
|
new Helper(HelperFunctionNames.ALL, value => {
|
||||||
let text = new SafeString(unescape(value).replace(/&/g, '&'))
|
let text = new SafeString(unescape(value).replace(/&/g, "&"))
|
||||||
if (text == null || typeof text !== "string") {
|
if (text == null || typeof text !== "string") {
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
return text.replace(/[<>]/g, tag => {
|
return text.replace(/[<>]/g, tag => {
|
||||||
return HTML_SWAPS[tag] || tag
|
return HTML_SWAPS[tag] || tag
|
||||||
})
|
})
|
||||||
})
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
module.exports.HelperFunctions = Object.values(HelperFunctionNames).concat(HelperFunctionBuiltin)
|
module.exports.HelperFunctions = Object.values(HelperFunctionNames).concat(
|
||||||
|
HelperFunctionBuiltin
|
||||||
|
)
|
||||||
|
|
||||||
module.exports.registerAll = handlebars => {
|
module.exports.registerAll = handlebars => {
|
||||||
for (let helper of HELPERS) {
|
for (let helper of HELPERS) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
const ALPHA_NUMERIC_REGEX = /^[A-Za-z0-9]+$/g
|
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) => {
|
module.exports.isAlphaNumeric = char => {
|
||||||
return char.match(ALPHA_NUMERIC_REGEX)
|
return char.match(ALPHA_NUMERIC_REGEX)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,13 @@ describe("Test that the string processing works correctly", () => {
|
||||||
expect(output).toBe("templating is easy")
|
expect(output).toBe("templating is easy")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should process a literal template", async () => {
|
||||||
|
const output = await processString("derp is {{{ adjective }}}", {
|
||||||
|
adjective: "derp"
|
||||||
|
})
|
||||||
|
expect(output).toBe("derp is derp")
|
||||||
|
})
|
||||||
|
|
||||||
it("should fail gracefully when wrong type passed in", async () => {
|
it("should fail gracefully when wrong type passed in", async () => {
|
||||||
let error = null
|
let error = null
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -33,3 +33,19 @@ describe("Handling context properties with spaces in their name", () => {
|
||||||
expect(output).toBe("testcase 1")
|
expect(output).toBe("testcase 1")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("attempt some complex problems", () => {
|
||||||
|
it("should be able to handle a very complex handlebars statement", async () => {
|
||||||
|
const context = {
|
||||||
|
"New Repeater": {
|
||||||
|
"Get Actors": {
|
||||||
|
"first_name": "Bob",
|
||||||
|
"last_name": "Bobert"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,33 @@
|
||||||
|
const { processString } = require("../src/index")
|
||||||
|
|
||||||
|
describe("specific test case for whether or not full app template can still be rendered", () => {
|
||||||
|
it("should be able to render the app template", async () => {
|
||||||
|
const template =
|
||||||
|
`<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{{{head}}}
|
||||||
|
</head>
|
||||||
|
<script>
|
||||||
|
window["##BUDIBASE_APP_ID##"] = "{{appId}}"
|
||||||
|
</script>
|
||||||
|
{{{body}}}
|
||||||
|
</html>`
|
||||||
|
const context = {
|
||||||
|
appId: "App1",
|
||||||
|
head: "<title>App</title>",
|
||||||
|
body: "<body><p>App things</p></body>"
|
||||||
|
}
|
||||||
|
const output = await processString(template, context)
|
||||||
|
expect(output).toBe(`<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>App</title>
|
||||||
|
</head>
|
||||||
|
<script>
|
||||||
|
window["##BUDIBASE_APP_ID##"] = "App1"
|
||||||
|
</script>
|
||||||
|
<body><p>App things</p></body>
|
||||||
|
</html>`)
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue