Merge branch 'feature/handlebars-migration' of github.com:Budibase/budibase into form-builder

This commit is contained in:
Andrew Kingston 2021-01-21 12:14:09 +00:00
commit 4cdca6f196
14 changed files with 160 additions and 83 deletions

View File

@ -11,9 +11,7 @@
on:click
class:big={subtitle != null}
{...$$restProps}>
{#if icon}
<i class={icon} />
{/if}
{#if icon}<i class={icon} />{/if}
<div class="content">
<div class="title">{title}</div>
{#if subtitle != null}

View File

@ -1,12 +1,6 @@
<script>
import groupBy from "lodash/fp/groupBy"
import {
Button,
TextArea,
Drawer,
Heading,
Spacer,
} from "@budibase/bbui"
import { Button, TextArea, Drawer, Heading, Spacer } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher()
@ -38,7 +32,9 @@
<Heading extraSmall>Tables</Heading>
<ul>
{#each context as { readableBinding }}
<li on:click={() => addToText(readableBinding)}>{readableBinding}</li>
<li on:click={() => addToText(readableBinding)}>
{readableBinding}
</li>
{/each}
</ul>
{/if}
@ -46,7 +42,9 @@
<Heading extraSmall>Components</Heading>
<ul>
{#each instance as { readableBinding }}
<li on:click={() => addToText(readableBinding)}>{readableBinding}</li>
<li on:click={() => addToText(readableBinding)}>
{readableBinding}
</li>
{/each}
</ul>
{/if}

View File

@ -2,8 +2,10 @@
import iconData from "./icons.js"
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>

View File

@ -177,6 +177,7 @@ exports.serveApp = async function(ctx) {
})
const appHbs = fs.readFileSync(`${__dirname}/templates/app.hbs`, "utf8")
console.log(appHbs)
ctx.body = await processString(appHbs, {
head,
body: html,

View File

@ -22,7 +22,9 @@
<title>{title}</title>
<link rel="icon" type="image/png" href={favicon} />
<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>
html,
body {

View File

@ -10,6 +10,4 @@
export let color = "#000"
</script>
<i
style={`color: ${color};`}
class={`${icon} ${size}`} />
<i style={`color: ${color};`} class={`${icon} ${size}`} />

View File

@ -191,4 +191,4 @@ module.exports = {
// Whether to use watchman for file crawling
// watchman: true,
};
}

View File

@ -11,7 +11,7 @@ export default {
name: "string-templates",
exports: "named",
globals: {
"fs": "fs",
fs: "fs",
},
},
external: ["fs"],

View File

@ -1,13 +1,26 @@
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) {
const output = fn(match)
const idx = string.indexOf(match)
return swapStrings(string, idx, match.length, output)
class Preprocessor {
constructor(name, fn) {
this.name = name
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 = [
new Preprocessor("swap-to-dot-notation", statement => {
let startBraceIdx = statement.indexOf("[")
let lastIdx = 0
while (startBraceIdx !== -1) {
@ -19,15 +32,17 @@ function swapToDotNotation(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
const exclusions = HelperFunctions.concat(["{{", "}}"])
// find all the parts split by spaces
const splitBySpaces = statement.split(" ")
// remove the excluded elements
const propertyParts = splitBySpaces.filter(part => exclusions.indexOf(part) === -1)
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
@ -35,14 +50,19 @@ function handleSpacesInProperties(statement) {
// 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}]`)
statement = swapStrings(
statement,
statement.indexOf(layer),
layer.length,
`[${layer}]`
)
}
}
// remove the edge case of double brackets being entered (in-case user already has specified)
return statement.replace(/\[\[/g, "[").replace(/]]/g, "]")
}
}),
function finalise(statement) {
new Preprocessor("finalise", statement => {
let insideStatement = statement.slice(2, statement.length - 2)
if (insideStatement.charAt(0) === " ") {
insideStatement = insideStatement.slice(1)
@ -54,7 +74,8 @@ function finalise(statement) {
insideStatement = `(${insideStatement})`
}
return `{{ all ${insideStatement} }}`
}
})
]
/**
* 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.
* @returns {string} The string that was input with processed up handlebars statements as required.
*/
module.exports.preprocess = (string) => {
let preprocessors = [swapToDotNotation, handleSpacesInProperties, finalise]
for (let processor of preprocessors) {
// re-run search each time incase previous cleaner update/removed a match
module.exports.preprocess = string => {
for (let processor of PROCESSORS) {
// re-run search each time incase previous processor updated/removed a match
let regex = new RegExp(FIND_HBS_REGEX)
let matches = string.match(regex)
if (matches == null) {
continue
}
for (let match of matches) {
string = handleProcessor(string, match, processor)
string = processor.process(string, match)
}
}
return string

View File

@ -12,7 +12,7 @@ const HelperFunctionBuiltin = [
"#each",
"#with",
"lookup",
"log"
"log",
]
const HelperFunctionNames = {
@ -27,17 +27,19 @@ const HELPERS = [
}),
// this help is applied to all statements
new Helper(HelperFunctionNames.ALL, value => {
let text = new SafeString(unescape(value).replace(/&amp;/g, '&'))
let text = new SafeString(unescape(value).replace(/&amp;/g, "&"))
if (text == null || typeof text !== "string") {
return text
}
return text.replace(/[<>]/g, 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 => {
for (let helper of HELPERS) {

View File

@ -1,8 +1,8 @@
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)
}

View File

@ -11,6 +11,13 @@ describe("Test that the string processing works correctly", () => {
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 () => {
let error = null
try {

View File

@ -33,3 +33,19 @@ describe("Handling context properties with spaces in their name", () => {
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")
})
})

View File

@ -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>`)
})
})