@reldens/cms 0.18.0 → 0.19.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.
- package/README.md +92 -9
- package/admin/reldens-admin-client.css +46 -2
- package/admin/reldens-admin-client.js +24 -0
- package/admin/templates/clear-all-cache-button.html +18 -0
- package/lib/admin-manager/contents-builder.js +7 -6
- package/lib/admin-manager/router-contents.js +58 -16
- package/lib/admin-manager-validator.js +2 -1
- package/lib/admin-manager.js +4 -2
- package/lib/admin-translations.js +9 -1
- package/lib/cache/add-cache-button-subscriber.js +53 -5
- package/lib/cache/cache-manager.js +47 -8
- package/lib/cache/cache-routes-handler.js +23 -0
- package/lib/cms-pages-route-manager.js +16 -4
- package/lib/frontend.js +188 -111
- package/lib/manager.js +42 -3
- package/lib/pagination-handler.js +243 -0
- package/lib/search-renderer.js +116 -0
- package/lib/search.js +344 -0
- package/lib/template-engine/collections-single-transformer.js +53 -0
- package/lib/template-engine/collections-transformer-base.js +84 -0
- package/lib/template-engine/collections-transformer.js +353 -0
- package/lib/template-engine/entities-transformer.js +65 -0
- package/lib/template-engine/partials-transformer.js +171 -0
- package/lib/template-engine.js +49 -435
- package/lib/templates-list.js +1 -0
- package/migrations/install.sql +18 -18
- package/package.json +4 -4
- package/templates/page.html +19 -2
- package/templates/partials/entriesListView.html +14 -0
- package/templates/partials/pagedCollection.html +33 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - CollectionsTransformer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { CollectionsTransformerBase } = require('./collections-transformer-base');
|
|
8
|
+
const { PaginationHandler } = require('../pagination-handler');
|
|
9
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
10
|
+
|
|
11
|
+
class CollectionsTransformer extends CollectionsTransformerBase
|
|
12
|
+
{
|
|
13
|
+
|
|
14
|
+
constructor(props)
|
|
15
|
+
{
|
|
16
|
+
super(props);
|
|
17
|
+
this.renderEngine = sc.get(props, 'renderEngine', false);
|
|
18
|
+
this.getPartials = sc.get(props, 'getPartials', false);
|
|
19
|
+
this.findAllPartialTags = sc.get(props, 'findAllPartialTags', false);
|
|
20
|
+
this.loadPartialTemplate = sc.get(props, 'loadPartialTemplate', false);
|
|
21
|
+
this.paginationHandler = new PaginationHandler();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getLoopCollectionStartRegex()
|
|
25
|
+
{
|
|
26
|
+
return /<collection\s+([^>]+)>/g;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getLoopCollectionEndRegex()
|
|
30
|
+
{
|
|
31
|
+
return new RegExp('<\\/collection>');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async transform(template, domain, req)
|
|
35
|
+
{
|
|
36
|
+
let processedTemplate = template;
|
|
37
|
+
let matches = [...template.matchAll(this.getLoopCollectionStartRegex())];
|
|
38
|
+
for(let i = matches.length - 1; i >= 0; i--){
|
|
39
|
+
let startMatch = matches[i];
|
|
40
|
+
let tagContent = startMatch[1];
|
|
41
|
+
if(tagContent.includes('field=')){
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
let tableName = this.extractAttributeValue(tagContent, 'name');
|
|
45
|
+
let filtersJson = this.extractAttributeValue(tagContent, 'filters');
|
|
46
|
+
let queryOptionsJson = this.extractAttributeValue(tagContent, 'data');
|
|
47
|
+
let relationsString = this.extractAttributeValue(tagContent, 'relations');
|
|
48
|
+
let paginationId = this.extractAttributeValue(tagContent, 'pagination');
|
|
49
|
+
if(paginationId && '' !== paginationId){
|
|
50
|
+
let containerName = this.extractAttributeValue(tagContent, 'container');
|
|
51
|
+
let prevPages = this.extractAttributeValue(tagContent, 'prevPages');
|
|
52
|
+
let nextPages = this.extractAttributeValue(tagContent, 'nextPages');
|
|
53
|
+
let loopResult = await this.processPaginatedCollection(
|
|
54
|
+
processedTemplate,
|
|
55
|
+
startMatch,
|
|
56
|
+
tableName,
|
|
57
|
+
filtersJson,
|
|
58
|
+
relationsString,
|
|
59
|
+
queryOptionsJson,
|
|
60
|
+
paginationId,
|
|
61
|
+
containerName,
|
|
62
|
+
prevPages,
|
|
63
|
+
nextPages,
|
|
64
|
+
domain,
|
|
65
|
+
req
|
|
66
|
+
);
|
|
67
|
+
if(loopResult){
|
|
68
|
+
processedTemplate = loopResult;
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
let loopResult = await this.processLoopCollection(
|
|
73
|
+
processedTemplate,
|
|
74
|
+
startMatch,
|
|
75
|
+
tableName,
|
|
76
|
+
filtersJson,
|
|
77
|
+
relationsString,
|
|
78
|
+
queryOptionsJson,
|
|
79
|
+
domain
|
|
80
|
+
);
|
|
81
|
+
if(loopResult){
|
|
82
|
+
processedTemplate = loopResult;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return processedTemplate;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async processLoopCollection(
|
|
89
|
+
template,
|
|
90
|
+
startMatch,
|
|
91
|
+
tableName,
|
|
92
|
+
filtersJson,
|
|
93
|
+
relationsString,
|
|
94
|
+
queryOptionsJson,
|
|
95
|
+
domain
|
|
96
|
+
){
|
|
97
|
+
return await this.processCollectionBase(
|
|
98
|
+
template,
|
|
99
|
+
startMatch,
|
|
100
|
+
tableName,
|
|
101
|
+
filtersJson,
|
|
102
|
+
relationsString,
|
|
103
|
+
queryOptionsJson,
|
|
104
|
+
domain,
|
|
105
|
+
false
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async processPaginatedCollection(
|
|
110
|
+
template,
|
|
111
|
+
startMatch,
|
|
112
|
+
tableName,
|
|
113
|
+
filtersJson,
|
|
114
|
+
relationsString,
|
|
115
|
+
queryOptionsJson,
|
|
116
|
+
paginationId,
|
|
117
|
+
containerName,
|
|
118
|
+
prevPages,
|
|
119
|
+
nextPages,
|
|
120
|
+
domain,
|
|
121
|
+
req
|
|
122
|
+
){
|
|
123
|
+
if(!req){
|
|
124
|
+
Logger.warning('No request provided for pagination, falling back to regular collection');
|
|
125
|
+
return await this.processLoopCollection(
|
|
126
|
+
template,
|
|
127
|
+
startMatch,
|
|
128
|
+
tableName,
|
|
129
|
+
filtersJson,
|
|
130
|
+
relationsString,
|
|
131
|
+
queryOptionsJson,
|
|
132
|
+
domain
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return await this.processCollectionBase(
|
|
136
|
+
template,
|
|
137
|
+
startMatch,
|
|
138
|
+
tableName,
|
|
139
|
+
filtersJson,
|
|
140
|
+
relationsString,
|
|
141
|
+
queryOptionsJson,
|
|
142
|
+
domain,
|
|
143
|
+
{
|
|
144
|
+
paginationId,
|
|
145
|
+
containerName,
|
|
146
|
+
prevPages,
|
|
147
|
+
nextPages,
|
|
148
|
+
req
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async processCollectionBase(
|
|
154
|
+
template,
|
|
155
|
+
startMatch,
|
|
156
|
+
tableName,
|
|
157
|
+
filtersJson,
|
|
158
|
+
relationsString,
|
|
159
|
+
queryOptionsJson,
|
|
160
|
+
domain,
|
|
161
|
+
paginationOptions
|
|
162
|
+
){
|
|
163
|
+
let startPos = startMatch.index;
|
|
164
|
+
let startEnd = startPos + startMatch[0].length;
|
|
165
|
+
let endMatch = this.getLoopCollectionEndRegex().exec(template.substring(startEnd));
|
|
166
|
+
if(!endMatch){
|
|
167
|
+
Logger.warning('No matching end tag found for collection: '+tableName);
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
let endPos = startEnd + endMatch.index;
|
|
171
|
+
let loopContent = template.substring(startEnd, endPos);
|
|
172
|
+
let collectionData;
|
|
173
|
+
let finalContent;
|
|
174
|
+
if(paginationOptions){
|
|
175
|
+
let templateParams = this.parseCollectionTemplateParams(filtersJson, queryOptionsJson);
|
|
176
|
+
let requestParams = this.paginationHandler.extractCollectionKeyFromRequest(
|
|
177
|
+
paginationOptions.req,
|
|
178
|
+
paginationOptions.paginationId
|
|
179
|
+
);
|
|
180
|
+
let mergedParams = this.paginationHandler.mergeCollectionParameters(
|
|
181
|
+
templateParams,
|
|
182
|
+
requestParams
|
|
183
|
+
);
|
|
184
|
+
mergedParams.prevPages = paginationOptions.prevPages;
|
|
185
|
+
mergedParams.nextPages = paginationOptions.nextPages;
|
|
186
|
+
let entity = this.dataServer.getEntity(tableName);
|
|
187
|
+
if(!entity){
|
|
188
|
+
Logger.warning('Entity not found in dataServer: '+tableName);
|
|
189
|
+
return template.substring(0, startPos) + '' + template.substring(endPos + endMatch[0].length);
|
|
190
|
+
}
|
|
191
|
+
let paginationData = this.paginationHandler.calculatePaginationData(
|
|
192
|
+
await this.paginationHandler.getCollectionTotal(entity, mergedParams.filters),
|
|
193
|
+
mergedParams.page,
|
|
194
|
+
mergedParams.limit,
|
|
195
|
+
this.getCurrentUrl(paginationOptions.req),
|
|
196
|
+
paginationOptions.paginationId,
|
|
197
|
+
mergedParams,
|
|
198
|
+
templateParams
|
|
199
|
+
);
|
|
200
|
+
let originalState = entity.preserveEntityState();
|
|
201
|
+
collectionData = await entity.loadEntityData(mergedParams.filters, {
|
|
202
|
+
limit: mergedParams.limit,
|
|
203
|
+
offset: paginationData.offset,
|
|
204
|
+
sortBy: mergedParams.sortBy,
|
|
205
|
+
sortDirection: mergedParams.sortDirection
|
|
206
|
+
}, relationsString);
|
|
207
|
+
entity.restoreEntityState(originalState);
|
|
208
|
+
if(this.jsonFieldsParser){
|
|
209
|
+
collectionData = this.jsonFieldsParser.parseJsonFields(
|
|
210
|
+
collectionData,
|
|
211
|
+
this.jsonFieldsParser.getJsonFieldsForEntity(tableName)
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
let collectionContent = await this.renderCollectionLoop(loopContent, collectionData, domain);
|
|
215
|
+
finalContent = this.renderEngine.render(
|
|
216
|
+
1 < paginationData.totalPages
|
|
217
|
+
? this.loadPaginationTemplate(paginationOptions.containerName, domain)
|
|
218
|
+
: '{{&collectionContentForCurrentPage}}',
|
|
219
|
+
Object.assign(
|
|
220
|
+
{},
|
|
221
|
+
paginationData,
|
|
222
|
+
{
|
|
223
|
+
collectionContentForCurrentPage: collectionContent,
|
|
224
|
+
hasResults: collectionData && 0 < collectionData.length,
|
|
225
|
+
noResultsMessage: 'No items found.'
|
|
226
|
+
}
|
|
227
|
+
),
|
|
228
|
+
this.getPartials(domain)
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
if(!paginationOptions){
|
|
232
|
+
collectionData = await this.fetchCollectionForTemplate(
|
|
233
|
+
tableName,
|
|
234
|
+
filtersJson,
|
|
235
|
+
queryOptionsJson,
|
|
236
|
+
relationsString
|
|
237
|
+
);
|
|
238
|
+
finalContent = await this.renderCollectionLoop(loopContent, collectionData, domain);
|
|
239
|
+
}
|
|
240
|
+
return template.substring(0, startPos) + finalContent + template.substring(endPos + endMatch[0].length);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
parseCollectionTemplateParams(filtersJson, queryOptionsJson)
|
|
244
|
+
{
|
|
245
|
+
let filters = {};
|
|
246
|
+
let queryOptions = {};
|
|
247
|
+
if(filtersJson && '' !== filtersJson.trim()){
|
|
248
|
+
let convertedFiltersJson = this.convertJsObjectToJson(filtersJson);
|
|
249
|
+
filters = sc.parseJson(convertedFiltersJson, {});
|
|
250
|
+
if(!filters){
|
|
251
|
+
Logger.warning('Invalid filters JSON: '+filtersJson);
|
|
252
|
+
filters = {};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if(queryOptionsJson && '' !== queryOptionsJson.trim()){
|
|
256
|
+
let convertedOptionsJson = this.convertJsObjectToJson(queryOptionsJson);
|
|
257
|
+
queryOptions = sc.parseJson(convertedOptionsJson, {});
|
|
258
|
+
if(!queryOptions){
|
|
259
|
+
Logger.warning('Invalid query options JSON: '+queryOptionsJson);
|
|
260
|
+
queryOptions = {};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
filters,
|
|
265
|
+
limit: sc.get(queryOptions, 'limit', 10),
|
|
266
|
+
sortBy: sc.get(queryOptions, 'sortBy', 'id'),
|
|
267
|
+
sortDirection: sc.get(queryOptions, 'sortDirection', 'asc')
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
loadPaginationTemplate(containerName, domain)
|
|
272
|
+
{
|
|
273
|
+
if(!containerName || '' === containerName){
|
|
274
|
+
containerName = 'pagedCollection';
|
|
275
|
+
}
|
|
276
|
+
let partialContent = this.loadPartialTemplate(containerName, domain);
|
|
277
|
+
if(partialContent){
|
|
278
|
+
return partialContent;
|
|
279
|
+
}
|
|
280
|
+
Logger.critical('Pagination template not found: ' + containerName + '. Please create the template file.');
|
|
281
|
+
return '{{&collectionContentForCurrentPage}}';
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async renderCollectionLoop(loopContent, collectionData, domain)
|
|
285
|
+
{
|
|
286
|
+
if(!collectionData || 0 === collectionData.length){
|
|
287
|
+
return this.renderNoResultsMessage(domain);
|
|
288
|
+
}
|
|
289
|
+
let renderedContent = '';
|
|
290
|
+
for(let row of collectionData){
|
|
291
|
+
renderedContent += await this.processPartialsInLoop(loopContent, row, domain);
|
|
292
|
+
}
|
|
293
|
+
return renderedContent;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
renderNoResultsMessage(domain)
|
|
297
|
+
{
|
|
298
|
+
let noResultsTemplate = this.loadPartialTemplate('noResults', domain);
|
|
299
|
+
if(noResultsTemplate){
|
|
300
|
+
return this.renderEngine.render(
|
|
301
|
+
noResultsTemplate,
|
|
302
|
+
{
|
|
303
|
+
message: 'No results found.',
|
|
304
|
+
cssClass: 'no-results',
|
|
305
|
+
alertClass: 'alert-info'
|
|
306
|
+
},
|
|
307
|
+
this.getPartials(domain)
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
return '';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async processPartialsInLoop(content, rowData, domain)
|
|
314
|
+
{
|
|
315
|
+
let processedContent = content;
|
|
316
|
+
let partialTags = this.findAllPartialTags(content);
|
|
317
|
+
for(let i = partialTags.length - 1; i >= 0; i--){
|
|
318
|
+
let tag = partialTags[i];
|
|
319
|
+
let partialContent = this.loadPartialTemplate(tag.name, domain);
|
|
320
|
+
if(!partialContent){
|
|
321
|
+
Logger.warning('Partial template not found: ' + tag.name);
|
|
322
|
+
processedContent = processedContent.substring(0, tag.start)+''+processedContent.substring(tag.end);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if(sc.hasOwn(tag.attributes, 'row')){
|
|
326
|
+
processedContent = processedContent.substring(0, tag.start)
|
|
327
|
+
+this.renderEngine.render(partialContent, {row: rowData}, this.getPartials(domain))
|
|
328
|
+
+processedContent.substring(tag.end);
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
let wrapperTemplate = '{{#vars}}{{> ' + tag.name + '}}{{/vars}}';
|
|
332
|
+
let renderData = { vars: tag.attributes };
|
|
333
|
+
let partials = {[tag.name]: partialContent};
|
|
334
|
+
processedContent = processedContent.substring(0, tag.start) +
|
|
335
|
+
this.renderEngine.render(wrapperTemplate, renderData, partials) +
|
|
336
|
+
processedContent.substring(tag.end);
|
|
337
|
+
}
|
|
338
|
+
return this.renderEngine.render(processedContent, {row: rowData}, this.getPartials(domain));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
getCurrentUrl(req)
|
|
342
|
+
{
|
|
343
|
+
if(!req){
|
|
344
|
+
return '';
|
|
345
|
+
}
|
|
346
|
+
return sc.get(req, 'protocol', 'http') + '://'
|
|
347
|
+
+ (req.get('host') || 'localhost')
|
|
348
|
+
+ sc.get(req, 'originalUrl', sc.get(req, 'path', '/')).split('?')[0];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
module.exports.CollectionsTransformer = CollectionsTransformer;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - EntitiesTransformer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
8
|
+
|
|
9
|
+
class EntitiesTransformer
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
constructor(props)
|
|
13
|
+
{
|
|
14
|
+
this.dataServer = sc.get(props, 'dataServer', false);
|
|
15
|
+
this.jsonFieldsParser = sc.get(props, 'jsonFieldsParser', false);
|
|
16
|
+
this.processAllTemplateFunctions = sc.get(props, 'processAllTemplateFunctions', false);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getEntityRegex()
|
|
20
|
+
{
|
|
21
|
+
return /<entity\s+name="([^"]+)"(?:\s+field="([^"]+)"\s+value="([^"]+)"|\s+id="([^"]+)")?\s*\/?>/g;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async transform(template, domain)
|
|
25
|
+
{
|
|
26
|
+
let processedTemplate = template;
|
|
27
|
+
for(let match of template.matchAll(this.getEntityRegex())){
|
|
28
|
+
let tableName = match[1];
|
|
29
|
+
let field = sc.get(match, '2', 'id');
|
|
30
|
+
let value = sc.get(match, '3', sc.get(match, '4', ''));
|
|
31
|
+
if(!value){
|
|
32
|
+
Logger.warning('Entity tag missing value: '+match[0]);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
processedTemplate = processedTemplate.replace(
|
|
36
|
+
match[0],
|
|
37
|
+
await this.processAllTemplateFunctions(
|
|
38
|
+
sc.get(await this.fetchEntityForTemplate(tableName, value, field), 'content', ''),
|
|
39
|
+
domain
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return processedTemplate;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async fetchEntityForTemplate(tableName, identifier, identifierField)
|
|
47
|
+
{
|
|
48
|
+
let entity = this.dataServer.getEntity(tableName);
|
|
49
|
+
if(!entity){
|
|
50
|
+
Logger.warning('Entity not found in dataServer: '+tableName);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
let result = await entity.loadOneBy(identifierField, identifier);
|
|
54
|
+
if(!result){
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return this.jsonFieldsParser.parseJsonFields(
|
|
58
|
+
result,
|
|
59
|
+
this.jsonFieldsParser.getJsonFieldsForEntity(tableName)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports.EntitiesTransformer = EntitiesTransformer;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - PartialsTransformer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
8
|
+
|
|
9
|
+
class PartialsTransformer
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
constructor(props)
|
|
13
|
+
{
|
|
14
|
+
this.renderEngine = sc.get(props, 'renderEngine', false);
|
|
15
|
+
this.getPartials = sc.get(props, 'getPartials', false);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async transform(template, domain)
|
|
19
|
+
{
|
|
20
|
+
let processedTemplate = template;
|
|
21
|
+
let partialTags = this.findAllPartialTags(template);
|
|
22
|
+
for(let i = partialTags.length - 1; i >= 0; i--){
|
|
23
|
+
let tag = partialTags[i];
|
|
24
|
+
let partialContent = this.loadPartialTemplate(tag.name, domain);
|
|
25
|
+
if(!partialContent){
|
|
26
|
+
Logger.warning('Partial template not found: ' + tag.name);
|
|
27
|
+
processedTemplate = processedTemplate.substring(0, tag.start)+''+processedTemplate.substring(tag.end);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
let wrapperTemplate = '{{#vars}}{{> ' + tag.name + '}}{{/vars}}';
|
|
31
|
+
let renderData = { vars: tag.attributes };
|
|
32
|
+
let partials = {[tag.name]: partialContent};
|
|
33
|
+
processedTemplate = processedTemplate.substring(0, tag.start) +
|
|
34
|
+
this.renderEngine.render(wrapperTemplate, renderData, partials) +
|
|
35
|
+
processedTemplate.substring(tag.end);
|
|
36
|
+
}
|
|
37
|
+
return processedTemplate;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
findAllPartialTags(template)
|
|
41
|
+
{
|
|
42
|
+
let partialTags = [];
|
|
43
|
+
let pos = 0;
|
|
44
|
+
let partial = '<partial';
|
|
45
|
+
for(let tagStart = template.indexOf(partial, pos); -1 !== tagStart; tagStart=template.indexOf(partial, pos)){
|
|
46
|
+
let tagEnd = this.findPartialTagEnd(template, tagStart);
|
|
47
|
+
if(-1 === tagEnd){
|
|
48
|
+
pos = tagStart + partial.length;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
let fullTag = template.substring(tagStart, tagEnd);
|
|
52
|
+
let nameMatch = fullTag.match(/name=["']([^"']+)["']/);
|
|
53
|
+
if(!nameMatch){
|
|
54
|
+
pos = tagStart + partial.length;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
let partialName = nameMatch[1];
|
|
58
|
+
let attributes = this.parsePartialAttributes(fullTag, partialName);
|
|
59
|
+
partialTags.push({
|
|
60
|
+
start: tagStart,
|
|
61
|
+
end: tagEnd,
|
|
62
|
+
name: partialName,
|
|
63
|
+
attributes: attributes,
|
|
64
|
+
fullTag: fullTag
|
|
65
|
+
});
|
|
66
|
+
pos = tagEnd;
|
|
67
|
+
}
|
|
68
|
+
return partialTags;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
findPartialTagEnd(template, tagStart)
|
|
72
|
+
{
|
|
73
|
+
let inQuotes = false;
|
|
74
|
+
let quoteChar = '';
|
|
75
|
+
let selfCloseTag = '/>';
|
|
76
|
+
let openCloseTag = '</partial>';
|
|
77
|
+
for(let i = tagStart; i < template.length; i++){
|
|
78
|
+
let char = template[i];
|
|
79
|
+
if(!inQuotes && ('"' === char || "'" === char)){
|
|
80
|
+
inQuotes = true;
|
|
81
|
+
quoteChar = char;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if(inQuotes && char === quoteChar && '\\' !== template[i - 1]){
|
|
85
|
+
inQuotes = false;
|
|
86
|
+
quoteChar = '';
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if(!inQuotes){
|
|
90
|
+
if(template.substring(i, i + selfCloseTag.length) === selfCloseTag){
|
|
91
|
+
return i + selfCloseTag.length;
|
|
92
|
+
}
|
|
93
|
+
if('>' === char){
|
|
94
|
+
let closeIndex = template.indexOf(openCloseTag, i);
|
|
95
|
+
if(-1 !== closeIndex){
|
|
96
|
+
return closeIndex + openCloseTag.length;
|
|
97
|
+
}
|
|
98
|
+
return i + 1;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return -1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
parsePartialAttributes(fullTag, partialName)
|
|
106
|
+
{
|
|
107
|
+
let namePattern = 'name=' + this.getQuotePattern(fullTag, partialName);
|
|
108
|
+
let nameIndex = fullTag.indexOf(namePattern);
|
|
109
|
+
if(-1 === nameIndex){
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
let attributesStart = nameIndex + namePattern.length;
|
|
113
|
+
let attributesEnd = fullTag.lastIndexOf('/>');
|
|
114
|
+
if(-1 === attributesEnd){
|
|
115
|
+
attributesEnd = fullTag.lastIndexOf('</partial>');
|
|
116
|
+
}
|
|
117
|
+
if(-1 === attributesEnd){
|
|
118
|
+
attributesEnd = fullTag.lastIndexOf('>');
|
|
119
|
+
}
|
|
120
|
+
if(-1 === attributesEnd || attributesEnd <= attributesStart){
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
let attributesString = fullTag.substring(attributesStart, attributesEnd).trim();
|
|
124
|
+
return this.extractAttributesObject(attributesString);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getQuotePattern(fullTag, partialName)
|
|
128
|
+
{
|
|
129
|
+
if(fullTag.includes('name="' + partialName + '"')){
|
|
130
|
+
return '"' + partialName + '"';
|
|
131
|
+
}
|
|
132
|
+
if(fullTag.includes("name='" + partialName + "'")){
|
|
133
|
+
return "'" + partialName + "'";
|
|
134
|
+
}
|
|
135
|
+
return '"' + partialName + '"';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
extractAttributesObject(attributesString)
|
|
139
|
+
{
|
|
140
|
+
if(!attributesString){
|
|
141
|
+
return {};
|
|
142
|
+
}
|
|
143
|
+
let attributes = {};
|
|
144
|
+
let valueRegex = /(\w+)=(['"])((?:(?!\2)[^\\]|\\.)*)(\2)/g;
|
|
145
|
+
for(let match of attributesString.matchAll(valueRegex)){
|
|
146
|
+
attributes[match[1]] = match[3];
|
|
147
|
+
}
|
|
148
|
+
let booleanRegex = /\b(\w+)(?!\s*=)/g;
|
|
149
|
+
for(let match of attributesString.matchAll(booleanRegex)){
|
|
150
|
+
if(!sc.hasOwn(attributes, match[1])){
|
|
151
|
+
attributes[match[1]] = true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return attributes;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
loadPartialTemplate(partialName, domain)
|
|
158
|
+
{
|
|
159
|
+
if(!this.getPartials){
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
let partials = this.getPartials(domain);
|
|
163
|
+
if(sc.hasOwn(partials, partialName)){
|
|
164
|
+
return partials[partialName];
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports.PartialsTransformer = PartialsTransformer;
|