Skip to content
Snippets Groups Projects
Commit 98856fa9 authored by Aaron Dötsch's avatar Aaron Dötsch
Browse files

Improve Handlebars implementation

- now first compiles handlebars and then markdown, instead of the other way around
- that improves markdown rendering, e.g. proper paragraphs instead of double line breaks and handlebars "~" working properly
- has markdown escaping now
parent 6e5fab81
No related branches found
No related tags found
No related merge requests found
Pipeline #7985 passed
...@@ -3,9 +3,8 @@ import Gender from "$lib/genders"; ...@@ -3,9 +3,8 @@ import Gender from "$lib/genders";
import { locales, type Locale } from "$lib/i18n/i18n"; import { locales, type Locale } from "$lib/i18n/i18n";
import type { Tutor } from "$lib/server/database/entities/Tutor.entity"; import type { Tutor } from "$lib/server/database/entities/Tutor.entity";
import markdownit from "markdown-it"; import markdownit from "markdown-it";
import type { Localized } from "$lib/utils"; import { mapLocalized, type Localized } from "$lib/utils";
import type { Config } from "$lib/server/config"; import type { Config } from "$lib/server/config";
import type { DateTimeFormatOptions } from "intl";
import Handlebars from "handlebars"; import Handlebars from "handlebars";
const md = markdownit({ const md = markdownit({
...@@ -238,13 +237,50 @@ export const conditions: Record<TemplateType, Condition[]> = { ...@@ -238,13 +237,50 @@ export const conditions: Record<TemplateType, Condition[]> = {
const bodyFormatter = ({ de, en }: Localized) => `-------- english version below --------\n\n${de}\n\n-------- english version --------\n\n${en}`; const bodyFormatter = ({ de, en }: Localized) => `-------- english version below --------\n\n${de}\n\n-------- english version --------\n\n${en}`;
const subjectFormatter = ({ de, en }: Localized) => [de, en].filter(s=>s).join(" / "); const subjectFormatter = ({ de, en }: Localized) => [de, en].filter(s=>s).join(" / ");
const originalEscapeExpression = Handlebars.Utils.escapeExpression;
const handlebars = {} as Localized<typeof Handlebars>;
for(const locale of locales){
handlebars[locale] = Handlebars.create();
handlebars[locale].registerHelper("date", (date, ...options)=>{
const formatOptions: Intl.DateTimeFormatOptions = {};
// last argument in options is from Handlebars, not provided by user
if(options.length > 1) formatOptions.year = options[0];
if(options.length > 2) formatOptions.month = options[1];
if(options.length > 3) formatOptions.day = options[2];
if(options.length > 4) formatOptions.hour = options[3];
if(options.length > 5) formatOptions.minute = options[4];
const formatter = new Intl.DateTimeFormat(locale, formatOptions);
return formatter.format(new Date(date));
});
}
export function compileTemplate(template: MailTemplate){ export function compileTemplate(template: MailTemplate){
const subjectTemplate = Handlebars.compile(subjectFormatter(template.subject)); const compiledSubject = {} as Localized<HandlebarsTemplateDelegate>;
const textTemplate = Handlebars.compile(bodyFormatter(template.text)); const compiledText = {} as Localized<HandlebarsTemplateDelegate>;
const htmlTemplate = Handlebars.compile(md.render(bodyFormatter(template.text))); const compiledHtml = {} as Localized<HandlebarsTemplateDelegate>;
for(const locale of locales){
compiledSubject[locale] = handlebars[locale].compile(template.subject[locale]);
compiledText[locale] = handlebars[locale].compile(template.text[locale]);
compiledHtml[locale] = handlebars[locale].compile(template.text[locale]);
}
return { return {
subject: subjectTemplate, subject: (context: any, options?: Handlebars.RuntimeOptions) => {
text: textTemplate, Handlebars.Utils.escapeExpression = str => String(str);
html: htmlTemplate, const subject = subjectFormatter(mapLocalized(compiledSubject, (template) => template(context, options)));
Handlebars.Utils.escapeExpression = originalEscapeExpression;
return subject;
},
text: (context: any, options?: Handlebars.RuntimeOptions) => {
Handlebars.Utils.escapeExpression = originalEscapeExpression;
return bodyFormatter(mapLocalized(compiledText, (template) => template(context, options)));
},
html: (context: any, options?: Handlebars.RuntimeOptions) => {
Handlebars.Utils.escapeExpression = str=>{
return originalEscapeExpression(String(str).replace(/([*#/()[\]_`\\-])/g, "\\$1")); // < and > dont need to be escaped, already happends by Handlebars
}
const html = md.render(bodyFormatter(mapLocalized(compiledHtml, (template) => template(context, options))));
Handlebars.Utils.escapeExpression = originalEscapeExpression;
return html;
},
}; };
} }
...@@ -112,3 +112,20 @@ export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pi ...@@ -112,3 +112,20 @@ export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pi
} }
return result; return result;
} }
export function shuffle<T>(array: T[]): T[]{
const shuffled = array.slice();
for(let i = shuffled.length - 1; i > 0; i--){
const j = Math.floor(Math.random() * (i + 1));
const temp = shuffled[i];
shuffled[i] = shuffled[j];
shuffled[j] = temp;
}
return shuffled;
}
export function mapLocalized<T, U>(obj: Localized<T>, fn: (value: T)=>U): Localized<U> {
return Object.fromEntries(
Object.entries(obj).map(([key, value])=>[key, fn(value)])
) as Localized<U>;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment