From 96ce320e4dc333d074af28cb11b50f31437d6eab Mon Sep 17 00:00:00 2001 From: Ronni Skansing Date: Thu, 4 Sep 2025 10:28:55 +0200 Subject: [PATCH] template function for date Signed-off-by: Ronni Skansing --- backend/service/templateService.go | 50 ++++++++++++++++++- .../src/lib/components/editor/Editor.svelte | 44 +++++++++++++++- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/backend/service/templateService.go b/backend/service/templateService.go index b02268c..3d0cad0 100644 --- a/backend/service/templateService.go +++ b/backend/service/templateService.go @@ -8,6 +8,7 @@ import ( "io" "math/rand" "strings" + "time" "github.com/go-errors/errors" "github.com/google/uuid" @@ -265,8 +266,9 @@ func (t *Template) newTemplateDataMap( // sender fields "From": mailHeaderFrom, // general fields - "BaseURL": baseURL, - "URL": url, + "BaseURL": baseURL, + "URL": url, + "APIKey": "", "CustomField1": "", "CustomField2": "", @@ -420,6 +422,15 @@ func TemplateFuncs() template.FuncMap { }, "randAlpha": RandAlpha, "qr": GenerateQRCode, + "date": func(format string, offsetSeconds ...int) string { + offset := 0 + if len(offsetSeconds) > 0 { + offset = offsetSeconds[0] + } + targetTime := time.Now().Add(time.Duration(offset) * time.Second) + goFormat := convertDateFormat(format) + return targetTime.Format(goFormat) + }, } } @@ -525,3 +536,38 @@ func (q *QRHTMLWriter) Close() error { } return nil } + +// convertDateFormat converts readable date format (YmdHis) to Go's reference format +func convertDateFormat(dateFormat string) string { + goFormat := dateFormat + + // year formats + goFormat = strings.ReplaceAll(goFormat, "Y", "2006") // 4-digit year + goFormat = strings.ReplaceAll(goFormat, "y", "06") // 2-digit year + + // month formats + goFormat = strings.ReplaceAll(goFormat, "m", "01") // 2-digit month + goFormat = strings.ReplaceAll(goFormat, "n", "1") // month without leading zero + goFormat = strings.ReplaceAll(goFormat, "M", "Jan") // short month name + goFormat = strings.ReplaceAll(goFormat, "F", "January") // full month name + + // day formats + goFormat = strings.ReplaceAll(goFormat, "d", "02") // 2-digit day + goFormat = strings.ReplaceAll(goFormat, "j", "2") // day without leading zero + + // hour formats + goFormat = strings.ReplaceAll(goFormat, "H", "15") // 24-hour format + goFormat = strings.ReplaceAll(goFormat, "h", "03") // 12-hour format + goFormat = strings.ReplaceAll(goFormat, "G", "15") // 24-hour without leading zero (Go doesn't support this exactly) + goFormat = strings.ReplaceAll(goFormat, "g", "3") // 12-hour without leading zero + + // minute and second formats + goFormat = strings.ReplaceAll(goFormat, "i", "04") // minutes + goFormat = strings.ReplaceAll(goFormat, "s", "05") // seconds + + // am/pm formats + goFormat = strings.ReplaceAll(goFormat, "A", "PM") // uppercase AM/PM + goFormat = strings.ReplaceAll(goFormat, "a", "pm") // lowercase am/pm + + return goFormat +} diff --git a/frontend/src/lib/components/editor/Editor.svelte b/frontend/src/lib/components/editor/Editor.svelte index 34cdd91..a3bd88b 100644 --- a/frontend/src/lib/components/editor/Editor.svelte +++ b/frontend/src/lib/components/editor/Editor.svelte @@ -59,7 +59,8 @@ { label: 'URL as QR HTML', text: '{{qr .URL 4}}' }, { label: 'URL escape', text: '{{urlEscape "content" }}' }, { label: 'Random alphanumeric', text: '{{randAlpha 8}}' }, - { label: 'Random number', text: '{{randInt 1 4}}' } + { label: 'Random number', text: '{{randInt 1 4}}' }, + { label: 'Date', text: '{{date "Y-m-d H:i:s" 0}}' } ] }; @@ -226,6 +227,19 @@ return result; }); } + + // handle date function with format and optional offset + if (text.includes('{{date')) { + const r = /{{date\s+"([^"]+)"\s*(-?\d+)?}}/g; + text = text.replace(r, (match, format, offset) => { + const currentTime = new Date(); + const offsetSeconds = offset ? parseInt(offset, 10) : 0; + const targetTime = new Date(currentTime.getTime() + offsetSeconds * 1000); + + return formatDate(targetTime, format); + }); + } + switch (contentType) { case 'domain': return text.replaceAll('{{.BaseURL}}', _baseURL); @@ -320,6 +334,34 @@ } }; + // formatDate converts readable date format (YmdHis) to formatted date string + const formatDate = (date, format) => { + const pad = (num, size = 2) => num.toString().padStart(size, '0'); + + const replacements = { + Y: date.getFullYear(), // 4-digit year + y: date.getFullYear().toString().slice(-2), // 2-digit year + m: pad(date.getMonth() + 1), // 2-digit month + n: date.getMonth() + 1, // month without leading zero + d: pad(date.getDate()), // 2-digit day + j: date.getDate(), // day without leading zero + H: pad(date.getHours()), // 24-hour format + G: date.getHours(), // 24-hour without leading zero + h: pad(date.getHours() % 12 || 12), // 12-hour format + g: date.getHours() % 12 || 12, // 12-hour without leading zero + i: pad(date.getMinutes()), // minutes + s: pad(date.getSeconds()), // seconds + A: date.getHours() >= 12 ? 'PM' : 'AM', // uppercase AM/PM + a: date.getHours() >= 12 ? 'pm' : 'am' // lowercase am/pm + }; + + let result = format; + for (const [key, value] of Object.entries(replacements)) { + result = result.replaceAll(key, value.toString()); + } + return result; + }; + let isDetailsVisible = $$slots.default;