@reldens/cms 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +260 -20
  2. package/admin/reldens-admin-client.css +127 -80
  3. package/admin/reldens-admin-client.js +24 -0
  4. package/admin/templates/clear-all-cache-button.html +18 -0
  5. package/lib/admin-manager/contents-builder.js +7 -6
  6. package/lib/admin-manager/router-contents.js +58 -16
  7. package/lib/admin-manager-validator.js +2 -1
  8. package/lib/admin-manager.js +4 -2
  9. package/lib/admin-translations.js +9 -1
  10. package/lib/cache/add-cache-button-subscriber.js +53 -5
  11. package/lib/cache/cache-manager.js +47 -8
  12. package/lib/cache/cache-routes-handler.js +23 -0
  13. package/lib/cms-pages-route-manager.js +16 -4
  14. package/lib/frontend.js +310 -119
  15. package/lib/manager.js +43 -3
  16. package/lib/pagination-handler.js +243 -0
  17. package/lib/search-renderer.js +116 -0
  18. package/lib/search.js +344 -0
  19. package/lib/template-engine/asset-transformer.js +41 -0
  20. package/lib/template-engine/collections-single-transformer.js +70 -0
  21. package/lib/template-engine/collections-transformer-base.js +84 -0
  22. package/lib/template-engine/collections-transformer.js +374 -0
  23. package/lib/template-engine/date-transformer.js +53 -0
  24. package/lib/template-engine/entities-transformer.js +67 -0
  25. package/lib/template-engine/partials-transformer.js +175 -0
  26. package/lib/template-engine/system-variables-provider.js +105 -0
  27. package/lib/template-engine/translate-transformer.js +98 -0
  28. package/lib/template-engine/translation-service.js +104 -0
  29. package/lib/template-engine/url-transformer.js +41 -0
  30. package/lib/template-engine.js +133 -438
  31. package/lib/templates-list.js +1 -0
  32. package/migrations/install.sql +18 -18
  33. package/package.json +4 -4
  34. package/templates/page.html +19 -2
  35. package/templates/partials/entriesListView.html +14 -0
  36. package/templates/partials/pagedCollection.html +33 -0
package/lib/frontend.js CHANGED
@@ -4,9 +4,11 @@
4
4
  *
5
5
  */
6
6
 
7
+ const { TemplateEngine } = require('./template-engine');
8
+ const { Search } = require('./search');
9
+ const { SearchRenderer } = require('./search-renderer');
7
10
  const { FileHandler } = require('@reldens/server-utils');
8
11
  const { Logger, sc } = require('@reldens/utils');
9
- const { TemplateEngine } = require('./template-engine');
10
12
 
11
13
  class Frontend
12
14
  {
@@ -17,6 +19,7 @@ class Frontend
17
19
  this.appServerFactory = sc.get(props, 'appServerFactory', false);
18
20
  this.dataServer = sc.get(props, 'dataServer', false);
19
21
  this.renderEngine = sc.get(props, 'renderEngine', false);
22
+ this.events = sc.get(props, 'events', false);
20
23
  this.projectRoot = sc.get(props, 'projectRoot', './');
21
24
  this.templatesPath = FileHandler.joinPaths(this.projectRoot, 'templates');
22
25
  this.publicPath = FileHandler.joinPaths(this.projectRoot, 'public');
@@ -31,6 +34,24 @@ class Frontend
31
34
  this.entitiesConfig = sc.get(props, 'entitiesConfig', {});
32
35
  this.templateEngine = false;
33
36
  this.cacheManager = sc.get(props, 'cacheManager', false);
37
+ this.searchPath = sc.get(props, 'searchPath', '/search');
38
+ this.searchSets = sc.get(props, 'searchSets', false);
39
+ this.searchConfig = {dataServer: this.dataServer};
40
+ if(this.searchSets){
41
+ this.searchConfig.searchSets = this.searchSets;
42
+ }
43
+ this.search = new Search(this.searchConfig);
44
+ this.searchRenderer = new SearchRenderer({
45
+ renderEngine: this.renderEngine,
46
+ getPartials: this.getPartialsForDomain.bind(this)
47
+ });
48
+ this.metaDefaults = sc.get(props, 'metaDefaults', {
49
+ locale: 'en',
50
+ viewport: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover',
51
+ meta_robots: 'index,follow',
52
+ meta_theme_color: '#000000',
53
+ meta_twitter_card_type: 'summary'
54
+ });
34
55
  }
35
56
 
36
57
  async initialize()
@@ -59,12 +80,20 @@ class Frontend
59
80
  renderEngine: this.renderEngine,
60
81
  dataServer: this.dataServer,
61
82
  getPartials: this.getPartialsForDomain.bind(this),
62
- entitiesConfig: this.entitiesConfig
83
+ entitiesConfig: this.entitiesConfig,
84
+ events: this.events,
85
+ defaultDomain: this.defaultDomain,
86
+ projectRoot: this.projectRoot,
87
+ publicPath: this.publicPath
63
88
  });
89
+ this.searchConfig.jsonFieldsParser = this.templateEngine.jsonFieldsParser;
64
90
  await this.loadPartials();
65
91
  await this.setupDomainTemplates();
66
92
  await this.loadEntityAccessRules();
67
93
  this.setupStaticAssets();
94
+ this.app.get(this.searchPath, async (req, res) => {
95
+ return await this.handleSearchRequest(req, res);
96
+ });
68
97
  this.app.get('*', async (req, res) => {
69
98
  return await this.handleRequest(req, res);
70
99
  });
@@ -249,31 +278,160 @@ class Frontend
249
278
  return false;
250
279
  }
251
280
 
281
+ fetchMetaFields(data)
282
+ {
283
+ if(!sc.isObject(data) || 0 === Object.keys(data).length){
284
+ return this.metaDefaults;
285
+ }
286
+ let result = Object.assign({}, this.metaDefaults);
287
+ for(let key of Object.keys(data)){
288
+ let value = data[key];
289
+ if(null !== value && '' !== value && 'undefined' !== typeof value){
290
+ result[key] = value;
291
+ }
292
+ }
293
+ let titleValue = sc.get(result, 'title', '');
294
+ let metaTitleValue = sc.get(result, 'meta_title', titleValue);
295
+ if(metaTitleValue && '' !== metaTitleValue){
296
+ result.meta_title = metaTitleValue;
297
+ }
298
+ let metaOgTitleValue = sc.get(result, 'meta_og_title', metaTitleValue);
299
+ if(metaOgTitleValue && '' !== metaOgTitleValue){
300
+ result.meta_og_title = metaOgTitleValue;
301
+ }
302
+ let metaDescValue = sc.get(result, 'meta_description', '');
303
+ let metaOgDescValue = sc.get(result, 'meta_og_description', metaDescValue);
304
+ if(metaOgDescValue && '' !== metaOgDescValue){
305
+ result.meta_og_description = metaOgDescValue;
306
+ }
307
+ let jsonData = sc.get(result, 'json_data', null);
308
+ if(sc.isString(jsonData)){
309
+ jsonData = sc.toJson(jsonData, {});
310
+ }
311
+ if(!sc.isObject(jsonData)){
312
+ jsonData = {};
313
+ }
314
+ let viewportValue = sc.get(jsonData, 'viewport', this.metaDefaults.viewport);
315
+ if(viewportValue && '' !== viewportValue){
316
+ jsonData.viewport = viewportValue;
317
+ }
318
+ result.json_data = jsonData;
319
+ return result;
320
+ }
321
+
322
+ async handleSearchRequest(req, res)
323
+ {
324
+ try {
325
+ let domain = this.getDomainFromRequest(req);
326
+ let config = this.search.parseSearchParameters(req.query);
327
+ if(!config){
328
+ return res.redirect('/?error-message=searchInvalidParameters');
329
+ }
330
+ let cacheKey = this.buildCacheKey(req.path, req);
331
+ if(this.cacheManager && this.cacheManager.isEnabled()){
332
+ let cachedContent = await this.cacheManager.get(domain, cacheKey);
333
+ if(cachedContent){
334
+ return res.send(cachedContent);
335
+ }
336
+ }
337
+ let searchResults = await this.search.executeSearch(config);
338
+ if(false === searchResults){
339
+ return res.redirect('/?error-message=searchExecutionFailed');
340
+ }
341
+ let content = await this.renderWithTemplateContent(
342
+ {
343
+ template: config.render.page,
344
+ layout: config.render.layout,
345
+ content: await this.searchRenderer.renderSearchResults(searchResults, config, domain, req)
346
+ },
347
+ Object.assign({}, {
348
+ search_query: sc.get(req.query, 'search', ''),
349
+ searchConfig: config,
350
+ query: req.query
351
+ }),
352
+ domain,
353
+ req,
354
+ null
355
+ );
356
+ if(this.cacheManager && this.cacheManager.isEnabled()){
357
+ await this.cacheManager.set(domain, cacheKey, content);
358
+ }
359
+ return res.send(content);
360
+ } catch (error) {
361
+ Logger.error('Search request handling error: ' + error.message);
362
+ return res.redirect('/?error-message=searchError');
363
+ }
364
+ }
365
+
366
+ normalizePathForRouteSearch(path)
367
+ {
368
+ if(!path || '/' === path){
369
+ return '/';
370
+ }
371
+ return path.endsWith('/') ? path.slice(0, -1) : path;
372
+ }
373
+
374
+ async handleRouteRedirect(route, res)
375
+ {
376
+ let redirectUrl = sc.get(route, 'redirect_url', null);
377
+ if(!redirectUrl){
378
+ return false;
379
+ }
380
+ let redirectType = sc.get(route, 'redirect_type', '301');
381
+ let statusCode = '301' === redirectType ? 301 : 302;
382
+ return res.redirect(statusCode, redirectUrl);
383
+ }
384
+
252
385
  async handleRequest(req, res)
253
386
  {
254
387
  try {
255
- let path = req.path;
388
+ let originalPath = req.path;
256
389
  let domain = this.getDomainFromRequest(req);
257
- this.templateEngine.setCurrentDomain(domain);
258
390
  if(this.cacheManager && this.cacheManager.isEnabled()){
259
- let cachedContent = await this.cacheManager.get(domain, path);
391
+ let cachedContent = await this.cacheManager.get(domain, originalPath);
260
392
  if(cachedContent){
261
393
  return res.send(cachedContent);
262
394
  }
263
395
  }
264
- let route = await this.findRouteByPath(path, domain);
396
+ let route = await this.findRouteByPath(originalPath, domain);
265
397
  if(route){
266
- return await this.renderRouteWithCache(route, domain, res, path);
398
+ let redirectResult = await this.handleRouteRedirect(route, res);
399
+ if(redirectResult){
400
+ return redirectResult;
401
+ }
402
+ let normalizedPath = this.normalizePathForRouteSearch(originalPath);
403
+ let routeFoundWithSlash = originalPath !== normalizedPath && route.path === normalizedPath + '/';
404
+ if(!originalPath.endsWith('/') && routeFoundWithSlash){
405
+ return res.redirect(301, originalPath + '/');
406
+ }
407
+ return await this.renderRouteWithCache(route, domain, res, originalPath, req);
408
+ }
409
+ let normalizedPath = this.normalizePathForRouteSearch(originalPath);
410
+ if(originalPath !== normalizedPath){
411
+ let alternativeRoute = await this.findRouteByPath(normalizedPath + '/', domain);
412
+ if(alternativeRoute){
413
+ let redirectResult = await this.handleRouteRedirect(alternativeRoute, res);
414
+ if(redirectResult){
415
+ return redirectResult;
416
+ }
417
+ if(!originalPath.endsWith('/')){
418
+ return res.redirect(301, originalPath + '/');
419
+ }
420
+ return await this.renderRouteWithCache(alternativeRoute, domain, res, originalPath, req);
421
+ }
267
422
  }
268
- let entityResult = await this.findEntityByPath(path);
423
+ if(!originalPath.endsWith('/') && '/' !== originalPath){
424
+ return res.redirect(301, originalPath + '/');
425
+ }
426
+ let entityResult = await this.findEntityByPath(originalPath);
269
427
  if(entityResult){
270
- return await this.renderEntityWithCache(entityResult, domain, res, path);
428
+ return await this.renderEntityWithCache(entityResult, domain, res, originalPath, req);
271
429
  }
272
- let templatePath = this.findTemplateByPath(path, domain);
430
+ let templatePath = this.findTemplateByPath(originalPath, domain);
273
431
  if(templatePath){
274
- return await this.renderTemplateWithCache(templatePath, domain, res, path);
432
+ return await this.renderTemplateWithCache(templatePath, domain, res, originalPath, req);
275
433
  }
276
- return await this.renderNotFound(domain, res);
434
+ return await this.renderNotFound(domain, res, req);
277
435
  } catch (error) {
278
436
  Logger.error('Request handling error: '+error.message);
279
437
  return res.status(500).send('Internal server error');
@@ -287,8 +445,9 @@ class Frontend
287
445
  Logger.error('Routes entity not found in dataServer.');
288
446
  return false;
289
447
  }
448
+ let normalizedPath = this.normalizePathForRouteSearch(path);
290
449
  let domainFilter = domain || null;
291
- let routeFilters = {path, enabled: 1};
450
+ let routeFilters = {path: normalizedPath, enabled: 1};
292
451
  let routes = await routesEntity.load(routeFilters);
293
452
  let matchingRoute = false;
294
453
  let nullDomain = false;
@@ -304,7 +463,25 @@ class Frontend
304
463
  if(matchingRoute){
305
464
  return matchingRoute;
306
465
  }
307
- return nullDomain;
466
+ if(nullDomain){
467
+ return nullDomain;
468
+ }
469
+ if(normalizedPath !== path){
470
+ let routeFiltersWithSlash = {path: path, enabled: 1};
471
+ let routesWithSlash = await routesEntity.load(routeFiltersWithSlash);
472
+ for(let route of routesWithSlash){
473
+ if(route.domain === domainFilter){
474
+ return route;
475
+ }
476
+ if(!route.domain){
477
+ nullDomain = route;
478
+ }
479
+ }
480
+ if(nullDomain){
481
+ return nullDomain;
482
+ }
483
+ }
484
+ return false;
308
485
  }
309
486
 
310
487
  async isEntityAccessible(entityName)
@@ -350,57 +527,46 @@ class Frontend
350
527
  return this.findTemplatePath(templatePath, domain);
351
528
  }
352
529
 
353
- async renderRouteWithCache(route, domain, res, path)
530
+ async renderRouteWithCache(route, domain, res, path, req)
354
531
  {
355
- let renderedContent = await this.generateRouteContent(route, domain);
532
+ let renderedContent = await this.generateRouteContent(route, domain, req);
356
533
  if(!renderedContent){
357
- return await this.renderNotFound(domain, res);
534
+ return await this.renderNotFound(domain, res, req);
358
535
  }
359
536
  if(this.cacheManager && this.cacheManager.isEnabled()){
360
- await this.cacheManager.set(domain, path, renderedContent);
537
+ let cacheKey = this.buildCacheKey(path, req);
538
+ await this.cacheManager.set(domain, cacheKey, renderedContent);
361
539
  }
362
540
  return res.send(renderedContent);
363
541
  }
364
542
 
365
- async renderEntityWithCache(entityResult, domain, res, path)
543
+ async renderEntityWithCache(entityResult, domain, res, path, req)
366
544
  {
367
- let renderedContent = await this.generateEntityContent(entityResult, domain);
545
+ let renderedContent = await this.generateEntityContent(entityResult, domain, req);
368
546
  if(!renderedContent){
369
- return await this.renderNotFound(domain, res);
547
+ return await this.renderNotFound(domain, res, req);
370
548
  }
371
549
  if(this.cacheManager && this.cacheManager.isEnabled()){
372
- await this.cacheManager.set(domain, path, renderedContent);
550
+ let cacheKey = this.buildCacheKey(path, req);
551
+ await this.cacheManager.set(domain, cacheKey, renderedContent);
373
552
  }
374
553
  return res.send(renderedContent);
375
554
  }
376
555
 
377
- async renderTemplateWithCache(templatePath, domain, res, path)
556
+ async renderTemplateWithCache(templatePath, domain, res, path, req)
378
557
  {
379
- let renderedContent = await this.generateTemplateContent(templatePath, domain);
558
+ let renderedContent = await this.generateTemplateContent(templatePath, domain, req);
380
559
  if(!renderedContent){
381
560
  return res.status(500).send('Template error: '+templatePath);
382
561
  }
383
562
  if(this.cacheManager && this.cacheManager.isEnabled()){
384
- await this.cacheManager.set(domain, path, renderedContent);
385
- }
386
- let layoutContent = await this.processContentWithLayout({content: renderedContent}, {}, 'default', domain);
387
- let pagePath = this.findTemplatePath('page', domain);
388
- if(!pagePath){
389
- return res.send(layoutContent);
563
+ let cacheKey = this.buildCacheKey(path, req);
564
+ await this.cacheManager.set(domain, cacheKey, renderedContent);
390
565
  }
391
- let pageTemplate = FileHandler.readFile(pagePath);
392
- if(!pageTemplate){
393
- return res.send(layoutContent);
394
- }
395
- let finalContent = await this.templateEngine.render(
396
- pageTemplate,
397
- Object.assign({}, {}, {content: layoutContent, siteHandle: this.resolveDomainToSiteKey(domain)}),
398
- this.getPartialsForDomain(domain)
399
- );
400
- return res.send(finalContent);
566
+ return await this.renderWithLayout({content: renderedContent}, {}, 'default', domain, res, req);
401
567
  }
402
568
 
403
- async generateRouteContent(route, domain)
569
+ async generateRouteContent(route, domain, req)
404
570
  {
405
571
  if(!route.router){
406
572
  return false;
@@ -413,78 +579,73 @@ class Frontend
413
579
  if(!content){
414
580
  return false;
415
581
  }
416
- return await this.renderWithTemplateContent(content, Object.assign({}, route, content), domain);
582
+ return await this.renderWithTemplateContent(content, Object.assign({}, route, content), domain, req, route);
417
583
  }
418
584
 
419
- async generateEntityContent(entityResult, domain)
585
+ async generateEntityContent(entityResult, domain, req)
420
586
  {
421
- return await this.renderWithTemplateContent(entityResult.entity, entityResult.entity, domain);
587
+ return await this.renderWithTemplateContent(entityResult.entity, entityResult.entity, domain, req, null);
422
588
  }
423
589
 
424
- async generateTemplateContent(templatePath, domain)
590
+ async generateTemplateContent(templatePath, domain, req)
425
591
  {
426
592
  let template = FileHandler.readFile(templatePath);
427
593
  if(!template){
428
594
  Logger.error('Failed to read template: ' + templatePath);
429
595
  return false;
430
596
  }
431
- return await this.templateEngine.render(template, {}, this.getPartialsForDomain(domain));
597
+ return await this.templateEngine.render(
598
+ template,
599
+ {},
600
+ this.getPartialsForDomain(domain),
601
+ domain,
602
+ req,
603
+ null,
604
+ null
605
+ );
432
606
  }
433
607
 
434
- async renderRoute(route, domain, res)
608
+ async renderRoute(route, domain, res, req)
435
609
  {
436
610
  if(!route.router){
437
- return await this.renderNotFound(domain, res);
611
+ return await this.renderNotFound(domain, res, req);
438
612
  }
439
613
  let entity = this.dataServer.getEntity(route.router);
440
614
  if(!entity){
441
- return await this.renderNotFound(domain, res);
615
+ return await this.renderNotFound(domain, res, req);
442
616
  }
443
617
  let content = await entity.loadOne({route_id: route.id});
444
618
  if(!content){
445
- return await this.renderNotFound(domain, res);
619
+ return await this.renderNotFound(domain, res, req);
446
620
  }
447
- return await this.renderWithTemplate(content, Object.assign({}, route, content), domain, res);
621
+ return await this.renderWithTemplate(content, Object.assign({}, route, content), domain, res, req);
448
622
  }
449
623
 
450
- async renderEntity(entityResult, domain, res)
624
+ async renderWithTemplate(content, data, domain, res, req)
451
625
  {
452
- return await this.renderWithTemplate(entityResult.entity, entityResult.entity, domain, res);
626
+ return res.send(await this.renderWithTemplateContent(content, data, domain, req, null));
453
627
  }
454
628
 
455
- async renderWithTemplate(content, data, domain, res)
629
+ async renderWithTemplateContent(content, data, domain, req, route)
456
630
  {
457
- let templateName = sc.get(content, 'template', '');
458
- let layoutName = sc.get(content, 'layout', 'default');
459
- let layoutContent = await this.processContentWithLayout(content, data, layoutName, domain);
631
+ let templateName = sc.get(content, 'template', 'page');
460
632
  if(!templateName){
461
633
  templateName = 'page';
462
634
  }
463
- let templatePath = this.findTemplatePath(templateName, domain);
464
- if(!templatePath){
465
- return res.send(layoutContent);
466
- }
467
- let pageTemplate = FileHandler.readFile(templatePath);
468
- if(!pageTemplate){
469
- return res.send(layoutContent);
470
- }
471
- return res.send(
472
- await this.templateEngine.render(
473
- pageTemplate,
474
- Object.assign({}, data, {content: layoutContent, siteHandle: this.resolveDomainToSiteKey(domain)}),
475
- this.getPartialsForDomain(domain)
476
- )
635
+ let layoutName = sc.get(content, 'layout', '');
636
+ if(!layoutName){
637
+ layoutName = 'default';
638
+ }
639
+ let currentEntityData = Object.assign({}, content, data);
640
+ let layoutContent = await this.processContentWithLayout(
641
+ content,
642
+ data,
643
+ layoutName,
644
+ domain,
645
+ req,
646
+ route,
647
+ currentEntityData
477
648
  );
478
- }
479
-
480
- async renderWithTemplateContent(content, data, domain)
481
- {
482
- let templateName = sc.get(content, 'template', '');
483
- let layoutName = sc.get(content, 'layout', 'default');
484
- let layoutContent = await this.processContentWithLayout(content, data, layoutName, domain);
485
- if(!templateName){
486
- templateName = 'page';
487
- }
488
649
  let templatePath = this.findTemplatePath(templateName, domain);
489
650
  if(!templatePath){
490
651
  return layoutContent;
@@ -495,53 +656,48 @@ class Frontend
495
656
  }
496
657
  return await this.templateEngine.render(
497
658
  pageTemplate,
498
- Object.assign({}, data, {content: layoutContent, siteHandle: this.resolveDomainToSiteKey(domain)}),
499
- this.getPartialsForDomain(domain)
659
+ Object.assign(
660
+ {},
661
+ this.fetchMetaFields(data),
662
+ {
663
+ content: layoutContent,
664
+ siteHandle: this.resolveDomainToSiteKey(domain)
665
+ }
666
+ ),
667
+ this.getPartialsForDomain(domain),
668
+ domain,
669
+ req,
670
+ route,
671
+ currentEntityData
500
672
  );
501
673
  }
502
674
 
503
- async renderTemplate(templatePath, domain, res)
504
- {
505
- let content = await this.renderContentWithTemplate(templatePath, {}, domain);
506
- if(!content){
507
- return res.status(500).send('Template error: '+templatePath);
508
- }
509
- return await this.renderWithLayout({content}, {}, 'default', domain, res);
510
- }
511
-
512
- async renderContentWithTemplate(templatePath, data, domain)
675
+ async renderContentWithTemplate(templatePath, data, domain, req)
513
676
  {
514
677
  let template = FileHandler.readFile(templatePath);
515
678
  if(!template){
516
679
  Logger.error('Failed to read template: ' + templatePath);
517
680
  return false;
518
681
  }
519
- return await this.templateEngine.render(template, data, this.getPartialsForDomain(domain));
682
+ return await this.templateEngine.render(
683
+ template,
684
+ data,
685
+ this.getPartialsForDomain(domain),
686
+ domain,
687
+ req,
688
+ null,
689
+ null
690
+ );
520
691
  }
521
692
 
522
- async renderWithLayout(content, data, layoutName, domain, res)
693
+ async renderWithLayout(content, data, layoutName, domain, res, req)
523
694
  {
524
- let layoutContent = await this.processContentWithLayout(content, data, layoutName, domain);
525
- let pagePath = this.findTemplatePath('page', domain);
526
- if(!pagePath){
527
- return res.send(layoutContent);
528
- }
529
- let pageTemplate = FileHandler.readFile(pagePath);
530
- if(!pageTemplate){
531
- return res.send(layoutContent);
532
- }
533
- return res.send(
534
- await this.templateEngine.render(
535
- pageTemplate,
536
- Object.assign({}, data, {content: layoutContent, siteHandle: this.resolveDomainToSiteKey(domain)}),
537
- this.getPartialsForDomain(domain)
538
- )
539
- );
695
+ return res.send(await this.renderWithTemplateContent(content, data, domain, req, null));
540
696
  }
541
697
 
542
- async processContentWithLayout(content, data, layoutName, domain)
698
+ async processContentWithLayout(content, data, layoutName, domain, req, route, currentEntityData)
543
699
  {
544
- let processedContent = await this.processContent(content, data, domain);
700
+ let processedContent = await this.processContent(content, data, domain, req, route, currentEntityData);
545
701
  let layoutPath = this.findLayoutPath(layoutName, domain);
546
702
  if(!layoutPath){
547
703
  return processedContent;
@@ -553,32 +709,67 @@ class Frontend
553
709
  return await this.templateEngine.render(
554
710
  layoutTemplate,
555
711
  Object.assign({}, data, {content: processedContent}),
556
- this.getPartialsForDomain(domain)
712
+ this.getPartialsForDomain(domain),
713
+ domain,
714
+ req,
715
+ route,
716
+ currentEntityData
557
717
  );
558
718
  }
559
719
 
560
- async processContent(content, data, domain)
720
+ async processContent(content, data, domain, req, route, currentEntityData)
561
721
  {
562
722
  let contentText = sc.get(content, 'content', '');
563
723
  if(!contentText){
564
724
  return '';
565
725
  }
566
- return await this.templateEngine.render(contentText, data, this.getPartialsForDomain(domain));
726
+ return await this.templateEngine.render(
727
+ contentText,
728
+ data,
729
+ this.getPartialsForDomain(domain),
730
+ domain,
731
+ req,
732
+ route,
733
+ currentEntityData
734
+ );
567
735
  }
568
736
 
569
- async renderNotFound(domain, res)
737
+ async renderNotFound(domain, res, req)
570
738
  {
571
739
  let notFoundPath = this.findTemplatePath('404', domain);
572
740
  if(notFoundPath){
573
- let content = await this.renderContentWithTemplate(notFoundPath, {}, domain);
741
+ let content = await this.renderContentWithTemplate(notFoundPath, {}, domain, req);
574
742
  if(content){
575
743
  res.status(404);
576
- return await this.renderWithLayout({content}, {}, 'default', domain, res);
744
+ return await this.renderWithLayout({content}, {}, 'default', domain, res, req);
577
745
  }
578
746
  }
579
747
  return res.status(404).send('Page not found');
580
748
  }
581
749
 
750
+ buildCacheKey(path, req)
751
+ {
752
+ if(!req || !req.query){
753
+ return path;
754
+ }
755
+ for(let key of Object.keys(req.query)){
756
+ if(key.endsWith('-key')){
757
+ let queryString = '';
758
+ for(let qKey of Object.keys(req.query)){
759
+ queryString += (queryString ? '&' : '') + qKey + '=' + req.query[qKey];
760
+ }
761
+ let hash = 0;
762
+ for(let i = 0; i < queryString.length; i++){
763
+ let char = queryString.charCodeAt(i);
764
+ hash = ((hash << 5) - hash) + char;
765
+ hash = hash & hash;
766
+ }
767
+ return path + '_' + Math.abs(hash);
768
+ }
769
+ }
770
+ return path;
771
+ }
772
+
582
773
  }
583
774
 
584
775
  module.exports.Frontend = Frontend;