Passed
Pull Request — master (#406)
by
unknown
01:44
created

NunjucksTemplates.createEnv   B

Complexity

Conditions 5

Size

Total Lines 41
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 32
dl 0
loc 41
rs 8.6453
c 0
b 0
f 0
1
import { Inject, Injectable } from '@nestjs/common';
2
import { NextFunction, Request, Response } from 'express';
3
import { NestExpressApplication } from '@nestjs/platform-express';
4
import { Environment, FileSystemLoader, TemplateCallback } from 'nunjucks';
5
import { runtime } from 'nunjucks';
6
import { ITemplates } from '../ITemplates';
7
import { RouteNameResolver } from '../../Common/ExtendedRouting/RouteNameResolver';
8
import { ITranslator } from 'src/Infrastructure/Translations/ITranslator';
9
import { formatFullName } from '../../Common/Utils/formatUtils';
10
import {
11
  formatDate,
12
  formatHtmlDate,
13
  minutesToHours
14
} from '../../Common/Utils/dateUtils';
15
import { ArrayUtils } from '../../Common/Utils/ArrayUtils';
16
import { TablesExtension } from './TablesExtension';
17
import { getMonth, getYear } from 'date-fns';
18
19
@Injectable()
20
export class NunjucksTemplates implements ITemplates {
21
  private env: Environment;
22
23
  constructor(
24
    private readonly resolver: RouteNameResolver,
25
    @Inject('ITranslator')
26
    private readonly translator: ITranslator
27
  ) {}
28
29
  private createEnv(viewsDir: string): Environment {
30
    const loader = new FileSystemLoader(viewsDir, {
31
      watch: process.env.NODE_ENV !== 'production'
32
    });
33
    const env = new Environment(loader);
34
35
    env.addFilter('trans', (key, params = {}) =>
36
      this.translator.translate(key, params)
37
    );
38
    env.addFilter('startswith', (value: string | null, other: string) =>
39
      value ? value.startsWith(other) : false
40
    );
41
    env.addFilter('minutesToHours', minutes => minutesToHours(minutes));
42
    env.addFilter('fullName', obj => formatFullName(obj));
43
    env.addFilter('date', value =>
44
      value === 'now' ? new Date() : formatDate(value)
45
    );
46
    env.addFilter('htmlDate', value => formatHtmlDate(value));
47
    env.addFilter('htmlYearMonth', value => formatHtmlDate(value).slice(0, 7));
48
    env.addFilter('longMonth', (month: number) =>
49
      this.translator.translate('common-month-long', {
50
        date: new Date(2023, month, 15)
51
      })
52
    );
53
    env.addFilter('year', (date: Date) => getYear(date));
54
    env.addFilter('zip', (left, right) => ArrayUtils.zip(left, right));
55
    env.addFilter('merge', (left, right) => {
56
      const result = {};
57
      for (const key in left) {
58
        result[key] = left[key];
59
      }
60
      for (const key in right) {
61
        result[key] = right[key];
62
      }
63
      return result;
64
    });
65
66
    env.addExtension('tables', new TablesExtension(this));
67
68
    return env;
69
  }
70
71
  registerViewEngine(app: NestExpressApplication, assetsRoot: string): void {
72
    const express = app.getHttpAdapter().getInstance();
73
74
    this.env = this.createEnv(express.get('views'));
75
76
    app.use((req: Request, res: Response, next: NextFunction): void => {
77
      const ctx = (res.locals.njkCtx = res.locals.njkCtx ?? {});
78
79
      ctx['req'] = req;
80
81
      ctx['path'] = (name: string, params: object = {}) => {
82
        try {
83
          return this.resolver.resolve(name, params);
84
        } catch {
85
          return '#';
86
        }
87
      };
88
89
      ctx['url'] = (name: string, params: object = {}) => {
90
        try {
91
          const path = this.resolver.resolve(name, params);
92
          const proto = req.protocol;
93
          const origin = req.get('Host');
94
          const baseUrl = `${proto}://${origin}`;
95
          const url = new URL(path, baseUrl);
96
          return url.toString();
97
        } catch {
98
          return '#';
99
        }
100
      };
101
102
      ctx['view_name'] = this.resolver.getName(req.url);
103
104
      ctx['asset'] = (path: string) => `${assetsRoot}/${path}`;
105
106
      ctx['now'] = new Date();
107
108
      ctx['theme'] = req.cookies.theme;
109
110
      next();
111
    });
112
113
    app.engine(
114
      'njk',
115
      (
116
        name: string,
117
        ctx: Record<string, any>,
118
        cb: TemplateCallback<string>
119
      ) => {
120
        this.env.render(name, { ...ctx, ...ctx._locals.njkCtx }, cb);
121
      }
122
    );
123
124
    app.setViewEngine('njk');
125
  }
126
127
  public render(name: string, context: object): string {
128
    return this.env.render(name, context);
129
  }
130
131
  public markSafe(html: string): any {
132
    return new runtime.SafeString(html);
133
  }
134
}
135