diff --git a/packages/string-templates/scripts/gen-collection-info.js b/packages/string-templates/scripts/gen-collection-info.js index 76b5c89e0c..863322fb29 100644 --- a/packages/string-templates/scripts/gen-collection-info.js +++ b/packages/string-templates/scripts/gen-collection-info.js @@ -5,18 +5,31 @@ 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 + * https://github.com/budibase/handlebars-helpers */ +const DIRECTORY = fs.existsSync("node_modules") ? "." : ".." const COLLECTIONS = ["math", "array", "number", "url", "string", "comparison"] - +const FILENAME = `${DIRECTORY}/manifest.json` const outputJSON = {} +const ADDED_HELPERS = { + date: { + date: { + args: ["datetime", "format"], + numArgs: 2, + example: '{{date now "DD-MM-YYYY"}} -> 21-01-2021', + description: "Format a date using moment.js date formatting.", + }, + duration: { + args: ["time", "durationType"], + numArgs: 2, + example: '{{duration timeLeft "seconds"}} -> a few seconds', + description: "Produce a humanized duration left/until given an amount of time and the type of time measurement." + } + } +} function fixSpecialCases(name, obj) { const args = obj.args @@ -134,15 +147,15 @@ function run() { } outputJSON[collection] = collectionInfo } - // add the date helper - outputJSON["date"] = { - date: { - args: ["datetime", "format"], - numArgs: 2, - example: '{{date now "DD-MM-YYYY"}}', - description: "Format a date using moment.js date formatting.", - }, + // add extra helpers + for (let [collectionName, collection] of Object.entries(ADDED_HELPERS)) { + let input = collection + if (outputJSON[collectionName]) { + input = Object.assign(outputJSON[collectionName], collection) + } + outputJSON[collectionName] = input } + // convert all markdown to HTML for (let collection of Object.values(outputJSON)) { for (let helper of Object.values(collection)) { diff --git a/packages/string-templates/src/helpers/date.js b/packages/string-templates/src/helpers/date.js index 1c929a758f..cc254195d9 100644 --- a/packages/string-templates/src/helpers/date.js +++ b/packages/string-templates/src/helpers/date.js @@ -1,4 +1,7 @@ const dayjs = require("dayjs") +dayjs.extend(require("dayjs/plugin/duration")) +dayjs.extend(require("dayjs/plugin/advancedFormat")) +dayjs.extend(require("dayjs/plugin/relativeTime")) /** * This file was largely taken from the helper-date package - we did this for two reasons: @@ -50,7 +53,7 @@ function getContext(thisArg, locals, options) { return context } -module.exports = function dateHelper(str, pattern, options) { +function initialSteps(str, pattern, options) { if (isOptions(pattern)) { options = pattern pattern = null @@ -61,6 +64,21 @@ module.exports = function dateHelper(str, pattern, options) { pattern = null str = null } + return {str, pattern, options} +} + +function setLocale(str, pattern, options) { + // if options is null then it'll get updated here + ({str, pattern, options} = initialSteps(str, pattern, options)) + const defaults = { lang: "en", date: new Date(str) } + const opts = getContext(this, defaults, options) + + // set the language to use + dayjs.locale(opts.lang || opts.language) +} + +module.exports.date = (str, pattern, options) => { + ({str, pattern, options} = initialSteps(str, pattern, options)) // if no args are passed, return a formatted date if (str == null && pattern == null) { @@ -68,11 +86,20 @@ module.exports = function dateHelper(str, pattern, options) { return dayjs().format("MMMM DD, YYYY") } - const defaults = { lang: "en", date: new Date(str) } - const opts = getContext(this, defaults, options) - - // set the language to use - dayjs.locale(opts.lang || opts.language) + setLocale(str, pattern, options) return dayjs(new Date(str)).format(pattern) } + +module.exports.duration = (str, pattern, format) => { + ({str, pattern} = initialSteps(str, pattern)) + + setLocale(str, pattern) + + const duration = dayjs.duration(str, pattern) + if (!isOptions(format)) { + return duration.format(format) + } else { + return duration.humanize() + } +} diff --git a/packages/string-templates/src/helpers/external.js b/packages/string-templates/src/helpers/external.js index 138565889d..df2880f96c 100644 --- a/packages/string-templates/src/helpers/external.js +++ b/packages/string-templates/src/helpers/external.js @@ -1,5 +1,5 @@ const helpers = require("@budibase/handlebars-helpers") -const dateHelper = require("./date") +const { date, duration } = require("./date") const { HelperFunctionBuiltin } = require("./constants") /** @@ -18,10 +18,15 @@ const EXTERNAL_FUNCTION_COLLECTIONS = [ "regex", ] -const DATE_NAME = "date" +const ADDED_HELPERS = { + "date": date, + "duration": duration, +} exports.registerAll = handlebars => { - handlebars.registerHelper(DATE_NAME, dateHelper) + for (let [name, helper] of Object.entries(ADDED_HELPERS)) { + handlebars.registerHelper(name, helper) + } let externalNames = [] for (let collection of EXTERNAL_FUNCTION_COLLECTIONS) { // collect information about helper @@ -43,12 +48,13 @@ exports.registerAll = handlebars => { }) } // add date external functionality - externalNames.push(DATE_NAME) - exports.externalHelperNames = externalNames + exports.externalHelperNames = externalNames.concat(Object.keys(ADDED_HELPERS)) } exports.unregisterAll = handlebars => { - handlebars.unregisterHelper(DATE_NAME) + for (let name of Object.keys(ADDED_HELPERS)) { + handlebars.unregisterHelper(name) + } for (let name of module.exports.externalHelperNames) { handlebars.unregisterHelper(name) } diff --git a/packages/string-templates/test/helpers.spec.js b/packages/string-templates/test/helpers.spec.js index b8b44a0929..89776583c9 100644 --- a/packages/string-templates/test/helpers.spec.js +++ b/packages/string-templates/test/helpers.spec.js @@ -318,6 +318,17 @@ describe("Cover a few complex use cases", () => { expect(validity).toBe(true) }) + it("test a very complex duration output", async () => { + const currentTime = new Date(1612432082000).toISOString(), + eventTime = new Date(1612432071000).toISOString() + const input = `{{duration ( subtract (date currentTime "X")(date eventTime "X")) "seconds"}}` + const output = await processString(input, { + currentTime, + eventTime, + }) + expect(output).toBe("a few seconds") + }) + it("should confirm a bunch of invalid strings", () => { const invalids = ["{{ awd )", "{{ awdd () ", "{{ awdwad ", "{{ awddawd }"] for (let invalid of invalids) {