@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
@@ -0,0 +1,105 @@
1
+ /**
2
+ *
3
+ * Reldens - CMS - SystemVariablesProvider
4
+ *
5
+ */
6
+
7
+ const { sc } = require('@reldens/utils');
8
+
9
+ class SystemVariablesProvider
10
+ {
11
+
12
+ constructor(props)
13
+ {
14
+ this.defaultDomain = sc.get(props, 'defaultDomain', 'default');
15
+ this.projectRoot = sc.get(props, 'projectRoot', './');
16
+ this.publicPath = sc.get(props, 'publicPath', './public');
17
+ }
18
+
19
+ buildSystemVariables(req, route, domain)
20
+ {
21
+ let currentRequest = this.buildCurrentRequestData(req, domain);
22
+ let currentRoute = this.buildCurrentRouteData(route);
23
+ let currentDomain = this.buildCurrentDomainData(domain);
24
+ let systemInfo = this.buildSystemInfo();
25
+ return {
26
+ currentRequest,
27
+ currentRoute,
28
+ currentDomain,
29
+ systemInfo
30
+ };
31
+ }
32
+
33
+ buildCurrentRequestData(req, domain)
34
+ {
35
+ if(!req){
36
+ return {};
37
+ }
38
+ let protocol = sc.get(req, 'protocol', 'http');
39
+ let host = req.get('host') || 'localhost';
40
+ let originalUrl = sc.get(req, 'originalUrl', sc.get(req, 'path', '/'));
41
+ let fullUrl = protocol+'://'+host+originalUrl;
42
+ return {
43
+ method: sc.get(req, 'method', 'GET'),
44
+ path: sc.get(req, 'path', '/'),
45
+ originalUrl,
46
+ fullUrl,
47
+ protocol,
48
+ host,
49
+ domain: domain || host.split(':')[0],
50
+ query: sc.get(req, 'query', {}),
51
+ params: sc.get(req, 'params', {}),
52
+ headers: sc.get(req, 'headers', {}),
53
+ userAgent: req.get('user-agent') || '',
54
+ ip: sc.get(req, 'ip', ''),
55
+ baseUrl: protocol+'://'+host,
56
+ timestamp: sc.getCurrentDate(),
57
+ isSecure: 'https' === protocol
58
+ };
59
+ }
60
+
61
+ buildCurrentRouteData(route)
62
+ {
63
+ if(!route){
64
+ return null;
65
+ }
66
+ return {
67
+ id: sc.get(route, 'id', null),
68
+ path: sc.get(route, 'path', ''),
69
+ router: sc.get(route, 'router', ''),
70
+ domain: sc.get(route, 'domain', null),
71
+ enabled: sc.get(route, 'enabled', false),
72
+ title: sc.get(route, 'title', ''),
73
+ template: sc.get(route, 'template', ''),
74
+ layout: sc.get(route, 'layout', ''),
75
+ meta_title: sc.get(route, 'meta_title', ''),
76
+ meta_description: sc.get(route, 'meta_description', ''),
77
+ sort_order: sc.get(route, 'sort_order', 0)
78
+ };
79
+ }
80
+
81
+ buildCurrentDomainData(domain)
82
+ {
83
+ return {
84
+ current: domain || this.defaultDomain,
85
+ default: this.defaultDomain,
86
+ resolved: domain || this.defaultDomain
87
+ };
88
+ }
89
+
90
+ buildSystemInfo()
91
+ {
92
+ return {
93
+ projectRoot: this.projectRoot,
94
+ publicPath: this.publicPath,
95
+ nodeVersion: process.version,
96
+ platform: process.platform,
97
+ environment: process.env.NODE_ENV || 'development',
98
+ timestamp: sc.getCurrentDate(),
99
+ uptime: process.uptime()
100
+ };
101
+ }
102
+
103
+ }
104
+
105
+ module.exports.SystemVariablesProvider = SystemVariablesProvider;
@@ -0,0 +1,98 @@
1
+ /**
2
+ *
3
+ * Reldens - CMS - TranslateTransformer
4
+ *
5
+ */
6
+
7
+ const { TranslationService } = require('./translation-service');
8
+ const { FileHandler } = require('@reldens/server-utils');
9
+ const { sc } = require('@reldens/utils');
10
+
11
+ class TranslateTransformer
12
+ {
13
+
14
+ constructor(props)
15
+ {
16
+ let projectRoot = sc.get(props, 'projectRoot', './');
17
+ let translationsPath = FileHandler.joinPaths(projectRoot, 'translations');
18
+ this.translationService = new TranslationService({
19
+ translationsPath,
20
+ defaultLocale: sc.get(props, 'defaultLocale', 'en'),
21
+ fallbackLocale: sc.get(props, 'fallbackLocale', 'en')
22
+ });
23
+ }
24
+
25
+ async transform(template, domain, req, systemVariables)
26
+ {
27
+ if(!template){
28
+ return template;
29
+ }
30
+ let currentLocale = this.extractLocaleFromSystemVariables(systemVariables);
31
+ let translatePattern = /\[(?:translate|t)\(([^)]+)\)\]/g;
32
+ let matches = [...template.matchAll(translatePattern)];
33
+ for(let i = matches.length - 1; i >= 0; i--){
34
+ let match = matches[i];
35
+ let args = this.parseTranslateArgs(match[1]);
36
+ let translatedText = await this.translationService.translate(
37
+ args.key,
38
+ currentLocale,
39
+ args.defaultValue,
40
+ args.interpolations
41
+ );
42
+ template = template.substring(0, match.index) +
43
+ translatedText +
44
+ template.substring(match.index + match[0].length);
45
+ }
46
+ return template;
47
+ }
48
+
49
+ parseTranslateArgs(argsString)
50
+ {
51
+ let args = argsString.split(',').map(arg => arg.trim().replace(/['"]/g, ''));
52
+ return {
53
+ key: args[0] || '',
54
+ defaultValue: args[1] || '',
55
+ interpolations: this.parseInterpolations(args[2])
56
+ };
57
+ }
58
+
59
+ parseInterpolations(interpolationsStr)
60
+ {
61
+ if(!interpolationsStr){
62
+ return {};
63
+ }
64
+ let interpolations = {};
65
+ let pairs = interpolationsStr.replace(/[{}]/g, '').split(',');
66
+ for(let pair of pairs){
67
+ let keyValue = pair.split(':');
68
+ if(2 === keyValue.length){
69
+ interpolations[keyValue[0].trim()] = keyValue[1].trim();
70
+ }
71
+ }
72
+ return interpolations;
73
+ }
74
+
75
+ extractLocaleFromSystemVariables(systemVariables)
76
+ {
77
+ let currentRequest = sc.get(systemVariables, 'currentRequest', {});
78
+ let headers = sc.get(currentRequest, 'headers', {});
79
+ let acceptLanguage = sc.get(headers, 'accept-language', '');
80
+ if(acceptLanguage){
81
+ let primaryLanguage = acceptLanguage.split(',')[0];
82
+ if(primaryLanguage && primaryLanguage.includes('-')){
83
+ return primaryLanguage.split('-')[0];
84
+ }
85
+ if(primaryLanguage){
86
+ return primaryLanguage;
87
+ }
88
+ }
89
+ let locale = sc.get(currentRequest, 'query', {}).locale;
90
+ if(locale){
91
+ return locale;
92
+ }
93
+ return this.translationService.defaultLocale;
94
+ }
95
+
96
+ }
97
+
98
+ module.exports.TranslateTransformer = TranslateTransformer;
@@ -0,0 +1,104 @@
1
+ /**
2
+ *
3
+ * Reldens - CMS - TranslationService
4
+ *
5
+ */
6
+
7
+ const { FileHandler } = require('@reldens/server-utils');
8
+ const { Logger, sc } = require('@reldens/utils');
9
+
10
+ class TranslationService
11
+ {
12
+
13
+ constructor(props)
14
+ {
15
+ this.translationsPath = sc.get(props, 'translationsPath', './translations');
16
+ this.defaultLocale = sc.get(props, 'defaultLocale', 'en');
17
+ this.fallbackLocale = sc.get(props, 'fallbackLocale', 'en');
18
+ this.translations = new Map();
19
+ this.loadedLocales = new Set();
20
+ }
21
+
22
+ async loadTranslations(locale)
23
+ {
24
+ if(this.loadedLocales.has(locale)){
25
+ return true;
26
+ }
27
+ let translationFile = FileHandler.joinPaths(this.translationsPath, locale + '.json');
28
+ if(!FileHandler.exists(translationFile)){
29
+ if(locale !== this.fallbackLocale){
30
+ Logger.warning('Translation file not found for locale: '+locale+', loading fallback');
31
+ return await this.loadTranslations(this.fallbackLocale);
32
+ }
33
+ Logger.warning('Translation file not found: '+translationFile);
34
+ return false;
35
+ }
36
+ let translationContent = FileHandler.readFile(translationFile);
37
+ if(!translationContent){
38
+ Logger.error('Failed to read translation file: '+translationFile);
39
+ return false;
40
+ }
41
+ let translationData = sc.toJson(translationContent, {});
42
+ if(!translationData){
43
+ Logger.error('Invalid JSON in translation file: '+translationFile);
44
+ return false;
45
+ }
46
+ this.translations.set(locale, translationData);
47
+ this.loadedLocales.add(locale);
48
+ return true;
49
+ }
50
+
51
+ async translate(key, locale, defaultValue, interpolations)
52
+ {
53
+ let targetLocale = locale || this.defaultLocale;
54
+ if(!this.loadedLocales.has(targetLocale)){
55
+ await this.loadTranslations(targetLocale);
56
+ }
57
+ let translations = this.translations.get(targetLocale);
58
+ let translatedText = this.getNestedTranslation(translations, key);
59
+ if(!translatedText && targetLocale !== this.fallbackLocale){
60
+ if(!this.loadedLocales.has(this.fallbackLocale)){
61
+ await this.loadTranslations(this.fallbackLocale);
62
+ }
63
+ let fallbackTranslations = this.translations.get(this.fallbackLocale);
64
+ translatedText = this.getNestedTranslation(fallbackTranslations, key);
65
+ }
66
+ if(!translatedText){
67
+ translatedText = defaultValue || key;
68
+ }
69
+ if(interpolations && sc.isObject(interpolations)){
70
+ translatedText = this.interpolateString(translatedText, interpolations);
71
+ }
72
+ return translatedText;
73
+ }
74
+
75
+ getNestedTranslation(translations, key)
76
+ {
77
+ if(!translations || !key){
78
+ return null;
79
+ }
80
+ let keys = key.split('.');
81
+ let current = translations;
82
+ for(let keyPart of keys){
83
+ if(!sc.hasOwn(current, keyPart)){
84
+ return null;
85
+ }
86
+ current = current[keyPart];
87
+ }
88
+ return sc.isString(current) ? current : null;
89
+ }
90
+
91
+ interpolateString(text, interpolations)
92
+ {
93
+ let result = text;
94
+ for(let key of Object.keys(interpolations)){
95
+ let placeholder = '{' + key + '}';
96
+ let value = interpolations[key];
97
+ result = result.replace(new RegExp(sc.sanitize(placeholder), 'g'), value);
98
+ }
99
+ return result;
100
+ }
101
+
102
+ }
103
+
104
+ module.exports.TranslationService = TranslationService;
@@ -0,0 +1,41 @@
1
+ /**
2
+ *
3
+ * Reldens - CMS - UrlTransformer
4
+ *
5
+ */
6
+
7
+ const { sc } = require('@reldens/utils');
8
+
9
+ class UrlTransformer
10
+ {
11
+
12
+ async transform(template, domain, req, systemVariables)
13
+ {
14
+ if(!template){
15
+ return template;
16
+ }
17
+ let currentRequest = sc.get(systemVariables, 'currentRequest', {});
18
+ let urlPattern = /\[url\(([^)]+)\)\]/g;
19
+ let matches = [...template.matchAll(urlPattern)];
20
+ for(let i = matches.length - 1; i >= 0; i--){
21
+ let match = matches[i];
22
+ let urlPath = match[1].replace(/['"]/g, '');
23
+ let absoluteUrl = this.buildAbsoluteUrl(urlPath, currentRequest);
24
+ template = template.substring(0, match.index) +
25
+ absoluteUrl +
26
+ template.substring(match.index + match[0].length);
27
+ }
28
+ return template;
29
+ }
30
+
31
+ buildAbsoluteUrl(relativePath, currentRequest)
32
+ {
33
+ if(!relativePath || relativePath.startsWith('http')){
34
+ return relativePath;
35
+ }
36
+ return sc.get(currentRequest, 'baseUrl', '') + relativePath;
37
+ }
38
+
39
+ }
40
+
41
+ module.exports.UrlTransformer = UrlTransformer;