forked from tsai/budibase
45 changed files with 2498 additions and 1102 deletions
@ -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 |
|||
|
|||
@ -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
@ -0,0 +1,173 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<title>Budibase self hosting️</title> |
|||
<style> |
|||
body { |
|||
font-family: Inter, -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; |
|||
height: 100%; |
|||
width: 100%; |
|||
margin: 0; |
|||
padding: 0; |
|||
background: #fafafa; |
|||
} |
|||
|
|||
.main { |
|||
padding: 0 20px; |
|||
margin: 30px auto; |
|||
width: 60%; |
|||
} |
|||
|
|||
h2 { |
|||
font-size: clamp(24px, 1.5vw, 30px); |
|||
text-align: center; |
|||
line-height: 1.3; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.card-grid { |
|||
display: grid; |
|||
grid-template-columns: 1fr 1fr; |
|||
gap: 3rem; |
|||
} |
|||
|
|||
.card { |
|||
display: grid; |
|||
background-color: #222222; |
|||
grid-template-columns: 1fr; |
|||
align-items: center; |
|||
padding: 2.5rem 1.75rem; |
|||
border-radius: 12px; |
|||
color: white; |
|||
} |
|||
|
|||
.card h3 { |
|||
margin: 0; |
|||
font-size: 24px; |
|||
font-family: sans-serif; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.card h3 b { |
|||
text-wrap: normal; |
|||
font-size: 36px; |
|||
padding-right: 14px; |
|||
} |
|||
|
|||
.card p { |
|||
color: #ffffff; |
|||
opacity: 0.8; |
|||
font-size: 18px; |
|||
text-align: left; |
|||
line-height: 1.3; |
|||
margin-top: 1rem; |
|||
} |
|||
|
|||
.logo { |
|||
width: 60px; |
|||
height: 60px; |
|||
margin: auto; |
|||
} |
|||
|
|||
.top-text { |
|||
text-align: center; |
|||
color: #707070; |
|||
margin: 0 0 1.5rem 0; |
|||
} |
|||
|
|||
.button { |
|||
cursor: pointer; |
|||
display: block; |
|||
background: #4285f4; |
|||
color: white; |
|||
padding: 12px 16px; |
|||
font-size: 16px; |
|||
font-weight: 600; |
|||
border-radius: 6px; |
|||
max-width: 120px; |
|||
text-align: center; |
|||
transition: 200ms background ease; |
|||
text-decoration: none; |
|||
} |
|||
|
|||
.info { |
|||
background: #f5f5f5; |
|||
padding: 1rem 1rem 1rem 1rem; |
|||
border: #ccc 1px solid; |
|||
border-radius: 6px; |
|||
margin-top: 40px; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.info p { |
|||
margin-left: 20px; |
|||
color: #222222; |
|||
font-family: sans-serif; |
|||
} |
|||
|
|||
.info p { |
|||
margin-right: 20px; |
|||
} |
|||
|
|||
.info svg { |
|||
margin-left: 20px; |
|||
} |
|||
|
|||
.info a { |
|||
color: #4285f4; |
|||
} |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<div class="main"> |
|||
<div class="logo"> |
|||
{{logo}} |
|||
</div> |
|||
<h2>Get started with Budibase Self Hosting</h2> |
|||
<p class="top-text">Use the address <b id="url"></b> in your Builder</p> |
|||
<div class="card-grid"> |
|||
<div class="card"> |
|||
<h3><b>📚</b>Documentation</h3> |
|||
<p> |
|||
Find out more about your self hosted platform. |
|||
</p> |
|||
<a class="button" |
|||
href="https://docs.budibase.com/self-hosting/introduction-to-self-hosting"> |
|||
Documentation |
|||
</a> |
|||
</div> |
|||
<div class="card"> |
|||
<h3><b>💻</b>Next steps</h3> |
|||
<p> |
|||
Find out how to make use of your self hosted Budibase platform. |
|||
</p> |
|||
<a class="button" |
|||
href="https://docs.budibase.com/self-hosting/builder-settings"> |
|||
Next steps |
|||
</a> |
|||
</div> |
|||
</div> |
|||
<div class="info"> |
|||
<svg preserveAspectRatio="xMidYMid meet" height="28px" width="28px" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" |
|||
xmlns:xlink="http://www.w3.org/1999/xlink" stroke="none" class="icon-7f6730be--text-3f89f380"> |
|||
<g> |
|||
<path d="M12.2 8.98c.06-.01.12-.03.18-.06.06-.02.12-.05.18-.09l.15-.12c.18-.19.29-.45.29-.71 0-.06-.01-.13-.02-.19a.603.603 0 0 0-.06-.19.757.757 0 0 0-.09-.18c-.03-.05-.08-.1-.12-.15-.28-.27-.72-.37-1.09-.21-.13.05-.23.12-.33.21-.04.05-.09.1-.12.15-.04.06-.07.12-.09.18-.03.06-.05.12-.06.19-.01.06-.02.13-.02.19 0 .26.11.52.29.71.1.09.2.16.33.21.12.05.25.08.38.08.06 0 .13-.01.2-.02M13 16v-4a1 1 0 1 0-2 0v4a1 1 0 1 0 2 0M12 3c-4.962 0-9 4.038-9 9 0 4.963 4.038 9 9 9 4.963 0 9-4.037 9-9 0-4.962-4.037-9-9-9m0 20C5.935 23 1 18.065 1 12S5.935 1 12 1c6.066 0 11 4.935 11 11s-4.934 11-11 11" fill-rule="evenodd"> |
|||
</path> |
|||
</g> |
|||
</svg> |
|||
<p>A <b>Hosting Key</b> will also be required, this can be found in your hosting properties, info found <a href="https://docs.budibase.com/self-hosting/hosting-settings">here</a>.</p> |
|||
</div> |
|||
</div> |
|||
<script> |
|||
window.addEventListener("load", () => { |
|||
let url = document.URL.split("//")[1] |
|||
if (url.substring(url.length - 1) === "/") { |
|||
url = url.substring(0, url.length - 1) |
|||
} |
|||
document.getElementById("url").innerHTML = url |
|||
}) |
|||
</script> |
|||
</body> |
|||
</html> |
|||
|
After Width: | Height: | Size: 4.6 KiB |
@ -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
@ -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 |
|||
@ -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 |
|||
}), |
|||
] |
|||
|
|||
Loading…
Reference in new issue