diff --git a/packages/string-templates/manifest.json b/packages/string-templates/manifest.json index af1f4e3d5..5a9688538 100644 --- a/packages/string-templates/manifest.json +++ b/packages/string-templates/manifest.json @@ -1097,8 +1097,17 @@ "format" ], "numArgs": 2, - "example": "{{date now \"DD-MM-YYYY\"}}", + "example": "{{date now \"DD-MM-YYYY\"}} -> 21-01-2021", "description": "
Format a date using moment.js date formatting.
\n" + }, + "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.
\n" } } } \ No newline at end of file diff --git a/packages/string-templates/scripts/gen-collection-info.js b/packages/string-templates/scripts/gen-collection-info.js index 76b5c89e0..a33d967a6 100644 --- a/packages/string-templates/scripts/gen-collection-info.js +++ b/packages/string-templates/scripts/gen-collection-info.js @@ -5,18 +5,32 @@ 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 +148,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 1c929a758..0d9bd7803 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 initialConfig(str, pattern, options) { if (isOptions(pattern)) { options = pattern pattern = null @@ -61,18 +64,42 @@ 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 + const config = initialConfig(str, pattern, options) + const defaults = { lang: "en", date: new Date(config.str) } + const opts = getContext(this, defaults, config.options) + + // set the language to use + dayjs.locale(opts.lang || opts.language) +} + +module.exports.date = (str, pattern, options) => { + const config = initialConfig(str, pattern, options) // if no args are passed, return a formatted date - if (str == null && pattern == null) { + if (config.str == null && config.pattern == null) { dayjs.locale("en") return dayjs().format("MMMM DD, YYYY") } - const defaults = { lang: "en", date: new Date(str) } - const opts = getContext(this, defaults, options) + setLocale(config.str, config.pattern, config.options) - // set the language to use - dayjs.locale(opts.lang || opts.language) + return dayjs(new Date(config.str)).format(config.pattern) +} - return dayjs(new Date(str)).format(pattern) +module.exports.duration = (str, pattern, format) => { + const config = initialConfig(str, pattern) + + setLocale(config.str, config.pattern) + + const duration = dayjs.duration(config.str, config.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 138565889..0fa7f734d 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 b8b44a092..89776583c 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) {