jsharmony-cms 1.3.0 → 1.5.1

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.
@@ -115,7 +115,9 @@ DataModelTemplate_GridPreview.prototype.buildTemplate = function(componentTempla
115
115
  model.oncommit = '_this.onCommit(xmodel, rowid, callback);';
116
116
  model.ejs = '';
117
117
  model.sort = { [this._sequenceFieldName]: 'asc' };
118
- model.oninit = "jsh.$root('.xform'+xmodel.class).before('<div class=\"dataGridEditor_instructions\"><span style=\"font-size:1.3em;position:relative;top:1px;margin-right:2px;margin-left:4px;\">&#x1f6c8;</span> Add, edit, and re-order items using the icons :: Double-click to edit</div>');";
118
+ model.oninit = [
119
+ "jsh.$root('.xform'+xmodel.class).before('<div class=\"dataGridEditor_instructions\"><span style=\"font-size:1.3em;position:relative;top:1px;margin-right:2px;margin-left:4px;\">&#x1f6c8;</span> Add, edit, and re-order items using the icons <span class=\"dataGridEditor_instructions_doubleClick\">:: Double-click to edit</span></div>');",
120
+ ].join(' ');
119
121
  model.rowclass = "<%=xejs.iif(rowid==0,'first')%>";
120
122
  model.tableclass = ((model.tableclass||'')+' '+(componentConfig&&componentConfig.options&&componentConfig.options.component_preview_size=='collapse'?'jsharmony_cms_component_preview_collapse':'jsharmony_cms_component_preview_expand')).trim();
121
123
  this._jsh.XPage.ParseModelDefinition(model, null, null, { ignoreErrors: true });
@@ -111,6 +111,8 @@ TemplateRenderer.render = function(config, type, jsh, cms, componentConfig, addi
111
111
  isInEditor: true,
112
112
  isInPageEditor: true,
113
113
  isInComponentEditor: ((type=='gridRowDataPreview') || (type=='gridItemPreview')),
114
+ componentRenderClass: 'jsharmony_cms_componentRender_'+jsh.XExt.escapeCSSClass((componentConfig&&componentConfig.id)||'')+'_'+cms.componentManager.getUniqueId().toString(),
115
+ getMediaThumbnails: function(url){ return cms.componentManager.getMediaThumbnails(url); },
114
116
  renderPlaceholder: renderPlaceholder,
115
117
  }, additionalRenderParams));
116
118
 
@@ -34,6 +34,8 @@ exports = module.exports = function(jsh, cms){
34
34
  this.containerlessComponents = {};
35
35
  this.cntContainerlessComponents = 0;
36
36
 
37
+ var maxUniqueId = 0;
38
+
37
39
  this.load = function(onError){
38
40
  _this.loadSystemComponentTemplates(onError);
39
41
  }
@@ -375,6 +377,8 @@ exports = module.exports = function(jsh, cms){
375
377
  isInEditor: true,
376
378
  isInPageEditor: false,
377
379
  isInComponentEditor: false,
380
+ componentRenderClass: 'jsharmony_cms_componentRender_'+XExt.escapeCSSClass((component&&component.id)||'')+'_'+cms.componentManager.getUniqueId().toString(),
381
+ getMediaThumbnails: function(url){ return cms.componentManager.getMediaThumbnails(url); },
378
382
  items: data.items,
379
383
  item: data.item,
380
384
  component: properties,
@@ -401,4 +405,24 @@ exports = module.exports = function(jsh, cms){
401
405
  }
402
406
  return rslt;
403
407
  }
408
+
409
+ this.getUniqueId = function(){ return ++maxUniqueId; }
410
+
411
+ this.getMediaThumbnails = function(url){
412
+ if(!cms.site_config || !cms.site_config.media_thumbnails) return {};
413
+ if(!url || (url.indexOf('#@JSHCMS') < 0)) return {};
414
+
415
+ var urlparts = document.createElement('a');
416
+ urlparts.href = url;
417
+ var patharr = (urlparts.pathname||'').split('/');
418
+ var rslt = {};
419
+ if(((urlparts.pathname||'').indexOf('/_funcs/media/')==0) && (patharr.length>=4)){
420
+ for(var thumbnail_id in cms.site_config.media_thumbnails){
421
+ if((patharr.length >= 5) && patharr[4]) patharr[4] = thumbnail_id;
422
+ else patharr.splice(4,0,thumbnail_id);
423
+ rslt[thumbnail_id] = urlparts.protocol + '//' + urlparts.host + patharr.join('/') + (urlparts.search||'') + (urlparts.hash||'');
424
+ }
425
+ }
426
+ return rslt;
427
+ }
404
428
  }
@@ -83,6 +83,9 @@ exports = module.exports = function(jsh, cms, toolbarContainer){
83
83
  ],
84
84
  contextmenu: 'jsharmonycmscomponentcontextmenu link linkchecker image imagetools table spellchecker configurepermanentpen',
85
85
  toolbar: 'formatselect | backcolor forecolor | bold italic underline | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image charmapmaterialicons table fullscreen | jsHarmonyCmsWebSnippet | jsHarmonyCmsComponent | jsHarmonyCmsView | jsHarmonyCmsEndEdit',
86
+ mobile: {
87
+ toolbar_mode: 'floating',
88
+ },
86
89
  removed_menuitems: 'newdocument',
87
90
  image_advtab: true,
88
91
  menu: {
@@ -13,11 +13,36 @@
13
13
  "template_variables": { // Default values for Template Variables
14
14
  "key": "value"
15
15
  },
16
+
17
+ //Media thumbnail configuration
16
18
  "media_thumbnails": {
19
+ "thumbnail_id": {
20
+ //(Optional) Resize thumbnail to fit within target width and height
21
+ "resize": [150, 150], // [width, height]
22
+
23
+ //(Optional) Crop thumbnail to target width and height
24
+ "crop": [150, 150], // [width, height]
25
+
26
+ //(Optional) Resample thumbnail to target format (jpg, png, gif, tif)
27
+ "format": "jpg",
28
+
29
+ //(Optional) Export thumbnail on publish, as filename.thumbnail_id.extension
30
+ "export": true,
31
+
32
+ //(Optional) Add as an srcset responsive breakpoint to IMG tags in published content
33
+ // If set to a string, the value will be used as the srcset condition, ex. "1x" "2x" "2048w"
34
+ // If set to true, the thumbnail width will be used as the srcset condition, ex "150w"
35
+ "responsive": true,
36
+ },
37
+
38
+ //Default Thumbnails
17
39
  "file_tile": { "resize": [150, 150], "format": "jpg" },
18
40
  "file_preview": { "resize": [300, 300], "format": "jpg" },
19
- "small": { "resize": [512, 384], "export": true },
20
- "medium": { "resize": [1024, 768], "export": true },
21
- "large": { "resize": [2048, 1538] }
41
+ "small": { "resize": [512, 384] },
42
+ "medium": { "resize": [1024, 768] },
43
+ "large": { "resize": [2048, 1538] },
44
+
45
+ //(Optional) Set a maximum image size for uploaded media
46
+ "maximum": { "resize": [2048, 1538] }
22
47
  }
23
48
  }
@@ -58,7 +58,7 @@ function jsHarmonyCMSConfig(){
58
58
  serverHttpsCa: undefined, // path/to/https-ca.pem
59
59
  };
60
60
 
61
- this.media_thumbnails = {
61
+ this.media_thumbnails = { //Media thumbnail configuration. See site_config.json for options
62
62
  file_tile: { resize: [150, 150], format: "jpg" },
63
63
  file_preview: { resize: [300, 300], format: "jpg" },
64
64
  small: { resize: [512, 384] },
@@ -14,6 +14,9 @@
14
14
  padding-right:19px;
15
15
  white-space:nowrap;
16
16
  }
17
+ .xform<%=model.class%> th a {
18
+ color:#555;
19
+ }
17
20
  .xform<%=model.class%> td {
18
21
  font-family: 'PT Sans', arial, helvetica, sans-serif;
19
22
  font-size:1.2em;
@@ -30,7 +30,7 @@
30
30
  <%% } %%>
31
31
  <%% _.each(cur_branch && branch_data, function(branch_item){ if(branch_item.code_val=='branch') return; %%>
32
32
  <tr>
33
- <th valign="bottom">&nbsp;&nbsp;<%%=branch_item.code_txt%%></th>
33
+ <th valign="bottom">&nbsp;&nbsp;<a href="<%%=jsh.globalparams.branch_items[branch_item.code_val].editor_url%%>"><%%=branch_item.code_txt%%></a></th>
34
34
  <td valign="bottom">
35
35
  <%%=branch_item.cnt%%>
36
36
  </td>
@@ -629,14 +629,14 @@ module.exports = exports = function(module, funcs){
629
629
  //Replace URLs
630
630
  function replaceURLs(content, options){
631
631
  var rslt = funcs.replaceBranchURLs(content, _.extend({ replaceComponents: true }, options, {
632
- getMediaURL: function(media_key, _branchData, getLinkContent){
632
+ getMediaURL: function(media_key, thumbnail_id, _branchData, getLinkContent){
633
633
  var orig_media_key = media_key;
634
634
  var media_file_id = media_key;
635
635
  if(branchData.media_mapping[orig_media_key]){
636
636
  media_key = branchData.media_mapping[orig_media_key].new_media_id;
637
637
  media_file_id = branchData.media_mapping[orig_media_key].new_media_file_id || media_key;
638
638
  }
639
- return branchData._baseurl+'_funcs/media/'+media_key+'/?media_file_id='+media_file_id+'#@JSHCMS';
639
+ return branchData._baseurl+'_funcs/media/'+media_key+(thumbnail_id?'/'+thumbnail_id:'')+'/?media_file_id='+media_file_id+'#@JSHCMS';
640
640
  },
641
641
  getPageURL: function(page_key){
642
642
  var orig_page_key = page_key;
@@ -659,9 +659,11 @@ module.exports = exports = function(module, funcs){
659
659
  isInEditor: false,
660
660
  isInPageEditor: false,
661
661
  isInComponentEditor: false,
662
+ componentRenderClass: 'jsharmony_cms_componentRender_'+Helper.escapeCSSClass((renderOptions.templateName)||'')+'_'+((branchData && branchData.component_getUniqueId && branchData.component_getUniqueId())||'').toString(),
662
663
  items: [],
663
664
  item: {},
664
665
  component: properties,
666
+ getMediaThumbnails: function(url){ return funcs.getMediaThumbnails(url, branchData); },
665
667
  renderPlaceholder: function(){ return ''; },
666
668
  renderTemplate: function(locals, templateName, items){
667
669
  if(!items || (_.isArray(items) && !items.length)) return '';
@@ -119,17 +119,19 @@ module.exports = exports = function(module, funcs){
119
119
  return page_fpath;
120
120
  }
121
121
 
122
- exports.getMediaRelativePath = function(media, publish_params, thumbnail_name){
122
+ exports.getMediaRelativePath = function(media, publish_params, thumbnail_id, thumbnail_config){
123
123
  var media_fpath = media.media_path||'';
124
124
  if(!media_fpath) return '';
125
125
  while(media_fpath.substr(0,1)=='/') media_fpath = media_fpath.substr(1);
126
126
 
127
- if(thumbnail_name){
127
+ if(thumbnail_id){
128
128
  var lastDot = media_fpath.lastIndexOf('.');
129
129
  var lastSlash = Math.max(media_fpath.lastIndexOf('/'),media_fpath.lastIndexOf('\\'));
130
- if((lastDot < 0) || (lastDot < lastSlash)) media_fpath += '.'+thumbnail_name;
130
+ if((lastDot < 0) || (lastDot < lastSlash)) media_fpath += '.'+thumbnail_id;
131
131
  else {
132
- media_fpath = media_fpath.substr(0, lastDot) + '.' + thumbnail_name + media_fpath.substr(lastDot);
132
+ media_fpath = media_fpath.substr(0, lastDot) +
133
+ '.' + thumbnail_id +
134
+ ((thumbnail_config && thumbnail_config.format) ? '.' + thumbnail_config.format : media_fpath.substr(lastDot));
133
135
  }
134
136
  }
135
137
 
@@ -142,6 +144,36 @@ module.exports = exports = function(module, funcs){
142
144
  return media_fpath;
143
145
  }
144
146
 
147
+ exports.getMediaThumbnails = function(url, branchData){
148
+ if(!branchData || !branchData.media_items || !branchData.site_config || !branchData.site_config.media_thumbnails) return {};
149
+ if(!url || (url.indexOf('#@JSHCMS') < 0)) return {};
150
+ var urlparts = null;
151
+ try{
152
+ urlparts = urlparser.parse(url, true);
153
+ }
154
+ catch(ex){
155
+ return {};
156
+ }
157
+ if(!urlparts.pathname) return {};
158
+ var patharr = (urlparts.pathname||'').split('/');
159
+
160
+ var rslt = {};
161
+ if((urlparts.pathname.indexOf('/_funcs/media/')==0) && (patharr.length>=4)){
162
+ var media_key = patharr[3];
163
+ if(!(media_key in branchData.media_items)) return {};
164
+ var media = branchData.media_items[media_key];
165
+ for(var thumbnail_id in branchData.site_config.media_thumbnails){
166
+ var thumbnail_config = branchData.site_config.media_thumbnails[thumbnail_id];
167
+ if(!thumbnail_config || !thumbnail_config.export) continue;
168
+ if(!_.includes(['.jpg','.jpeg','.tif','.tiff','.png','.gif','.svg'], media.media_ext)) continue;
169
+ if((patharr.length >= 5) && patharr[4]) patharr[4] = thumbnail_id;
170
+ else patharr.splice(4,0,thumbnail_id);
171
+ rslt[thumbnail_id] = urlparts.protocol + '//' + urlparts.host + patharr.join('/') + (urlparts.search||'') + (urlparts.hash||'');
172
+ }
173
+ }
174
+ return rslt;
175
+ }
176
+
145
177
  exports.downloadLocalTemplates = function(branchData, templates, template_html, options, download_cb){
146
178
  options = _.extend({ templateType: 'PAGE', exportTemplates: {} }, options);
147
179
 
@@ -592,6 +624,8 @@ module.exports = exports = function(module, funcs){
592
624
  deployment.publish_params = publish_params;
593
625
 
594
626
  //Branch Data
627
+ var component_maxUniqueId = 0;
628
+ var component_maxUniqueIdSalt = crypto.randomBytes(16).toString('hex');
595
629
  var branchData = {
596
630
  publish_params: publish_params,
597
631
  template_variables: template_variables,
@@ -612,8 +646,10 @@ module.exports = exports = function(module, funcs){
612
646
  component_templates: null,
613
647
  component_template_html: {},
614
648
  component_export_template_html: {},
649
+ component_getUniqueId: function(){ return crypto.createHash('md5').update(component_maxUniqueIdSalt+'.'+(++component_maxUniqueId).toString()).digest("hex"); },
615
650
 
616
651
  media_keys: {},
652
+ media_items: {},
617
653
 
618
654
  menus: {},
619
655
  menu_template_html: {},
@@ -1289,7 +1325,7 @@ module.exports = exports = function(module, funcs){
1289
1325
 
1290
1326
  //Get list of all media_keys
1291
1327
  var sql = 'select \
1292
- m.media_key,media_file_id,media_path \
1328
+ m.media_key,media_file_id,media_path,media_width,media_height,media_ext \
1293
1329
  from '+(module.schema?module.schema+'.':'')+'media m \
1294
1330
  inner join '+(module.schema?module.schema+'.':'')+'branch_media bm on bm.media_id = m.media_id\
1295
1331
  inner join '+(module.schema?module.schema+'.':'')+'deployment d on d.branch_id = bm.branch_id and d.deployment_id=@deployment_id\
@@ -1311,7 +1347,10 @@ module.exports = exports = function(module, funcs){
1311
1347
  }
1312
1348
  catch(ex){ }
1313
1349
 
1314
- if(media_urlpath) branchData.media_keys[media.media_key] = media_urlpath;
1350
+ if(media_urlpath){
1351
+ branchData.media_keys[media.media_key] = media_urlpath;
1352
+ branchData.media_items[media.media_key] = media;
1353
+ }
1315
1354
  });
1316
1355
  return cb();
1317
1356
  });
@@ -1416,10 +1455,12 @@ module.exports = exports = function(module, funcs){
1416
1455
  //Additional parameters for static render
1417
1456
  page: clientPage.page,
1418
1457
  template: clientPage.template,
1458
+
1419
1459
  sitemap: clientPage.sitemap,
1420
1460
  getSitemapURL: function(sitemap_item){ return funcs.getSitemapUrl(sitemap_item, branchData); },
1421
1461
  menu: null,
1422
1462
  getMenuURL: function(menu_item){ return funcs.getMenuUrl(menu_item, branchData); },
1463
+
1423
1464
  include: includePage,
1424
1465
  }
1425
1466
  if(renderOptions.menu_tag){
@@ -1465,9 +1506,14 @@ module.exports = exports = function(module, funcs){
1465
1506
  include: includePage,
1466
1507
  });
1467
1508
  renderedContent = funcs.applyRenderTags(renderedContent, { page: ejsparams.page });
1509
+ renderedContent = funcs.applyResponsiveImg(renderedContent, branchData.site_config.media_thumbnails, branchData.media_items);
1468
1510
  var replaceBranchURLsParams = {
1469
- getMediaURL: function(media_key){
1511
+ getMediaURL: function(media_key, thumbnail_id){
1470
1512
  if(!(media_key in branchData.media_keys)) throw new Error('Page '+page.page_path+' links to missing Media ID # '+media_key.toString());
1513
+ if(thumbnail_id){
1514
+ if(!(branchData.site_config.media_thumbnails && branchData.site_config.media_thumbnails[thumbnail_id])) throw new Error('Page '+page.page_path+', Media #'+media_key.toString()+' links to invalid Thumbnail ID: '+thumbnail_id);
1515
+ return funcs.appendThumbnail(branchData.media_keys[media_key], thumbnail_id, branchData.site_config.media_thumbnails && branchData.site_config.media_thumbnails[thumbnail_id]);
1516
+ }
1471
1517
  return branchData.media_keys[media_key];
1472
1518
  },
1473
1519
  getPageURL: function(page_key){
@@ -1589,7 +1635,7 @@ module.exports = exports = function(module, funcs){
1589
1635
  _.each(renderParams.menu.items, function(menu_item){ menu_item.selected = false; });
1590
1636
  }
1591
1637
 
1592
- var rslt = funcs.renderComponent(branchData.component_export_template_html[template_name][exportIndex] || '', null, renderOptions, renderParams);
1638
+ var rslt = funcs.renderComponent(branchData.component_export_template_html[template_name][exportIndex] || '', branchData, renderOptions, renderParams);
1593
1639
  if(exportItem.export_path){
1594
1640
  branchData.fsOps.addFile(branchData.fsOps.getValidFilePath(exportItem.export_path), rslt);
1595
1641
  }
@@ -1787,24 +1833,24 @@ module.exports = exports = function(module, funcs){
1787
1833
  },
1788
1834
  //Export thumbnails
1789
1835
  function(generate_cb){
1790
- async.eachOfSeries(branchData.site_config.media_thumbnails, function(thumbnail_config, thumbnail_name, thumbnail_cb){
1836
+ async.eachOfSeries(branchData.site_config.media_thumbnails, function(thumbnail_config, thumbnail_id, thumbnail_cb){
1791
1837
  if(!thumbnail_config || !thumbnail_config.export) return thumbnail_cb();
1792
- if(!_.includes(['.jpg','.jpeg','.tif','.tiff','.png','.gif','.svg'], media.media_ext)) return cb();
1838
+ if(!_.includes(['.jpg','.jpeg','.tif','.tiff','.png','.gif','.svg'], media.media_ext)) return thumbnail_cb();
1793
1839
 
1794
- funcs.getMediaFile(media.media_file_id, media.media_filename, media.media_ext, thumbnail_name, thumbnail_config, function(err, thumbnail_srcpath, stat){
1795
- if(err) return thumbnail_cb('Error getting thumbnail for '+media_fpath+':'+thumbnail_name+' - '+err.toString());
1840
+ funcs.getMediaFile(media.media_file_id, media.media_filename, media.media_ext, thumbnail_id, thumbnail_config, function(err, thumbnail_srcpath, thumbnail_fname, stat){
1841
+ if(err) return thumbnail_cb('Error getting thumbnail for '+media_fpath+':'+thumbnail_id+' - '+err.toString());
1796
1842
 
1797
1843
  fs.readFile(thumbnail_srcpath, null, function(err, thumbnail_content){
1798
1844
  if(err) return thumbnail_cb(err);
1799
1845
 
1800
1846
  var thumbnail_fpath = '';
1801
1847
  try{
1802
- thumbnail_fpath = funcs.getMediaRelativePath(media, publish_params, thumbnail_name);
1848
+ thumbnail_fpath = funcs.getMediaRelativePath(media, publish_params, thumbnail_id, thumbnail_config);
1803
1849
  }
1804
1850
  catch(ex){
1805
1851
  return thumbnail_cb(ex);
1806
1852
  }
1807
- if(!thumbnail_fpath) return thumbnail_cb(new Error('Thumbnail deployment path could not be generated for: '+media.media_fpath+':'+thumbnail_name));
1853
+ if(!thumbnail_fpath) return thumbnail_cb(new Error('Thumbnail deployment path could not be generated for: '+media.media_fpath+':'+thumbnail_id));
1808
1854
 
1809
1855
  branchData.site_files[thumbnail_fpath] = {
1810
1856
  md5: crypto.createHash('md5').update(thumbnail_content).digest("hex")
@@ -888,6 +888,11 @@ module.exports = exports = function(module, funcs){
888
888
  }
889
889
  else {
890
890
  wc.req(op.params[0], 'GET', {}, {}, undefined, function(err, res, templateContent){
891
+ if(err){
892
+ if((err.code=='DEPTH_ZERO_SELF_SIGNED_CERT')||(err.code=='SELF_SIGNED_CERT_IN_CHAIN')||(err.message=='self signed certificate')){
893
+ err.message = 'Self-signed certificate found for URL: '+op.params[0] + ' :: Select the "Ignore certificate errors when downloading remote templates" option in the Deployment Target configuration to allow self-signed certificates.';
894
+ }
895
+ }
891
896
  op.params[1](err, res, templateContent, op.done);
892
897
  }, { rejectUnauthorized: !publish_config.ignore_remote_template_certificate });
893
898
  }
@@ -29,22 +29,22 @@ module.exports = exports = function(module, funcs){
29
29
  var exports = {};
30
30
  var _t = module._t, _tN = module._tN;
31
31
 
32
- exports.getMediaFilename = function(media_file_id, media_ext, thumbnail_name, thumbnail_config){
32
+ exports.getMediaFilename = function(media_file_id, media_ext, thumbnail_id, thumbnail_config){
33
33
  var fname = media_file_id.toString();
34
- if(thumbnail_name) fname += '.' + thumbnail_name;
34
+ if(thumbnail_id) fname += '.' + thumbnail_id;
35
35
  else if(thumbnail_config && thumbnail_config.resize){
36
36
  fname += '.custom.'+(thumbnail_config.resize[0]||'').toString()+'x'+(thumbnail_config.resize[1]||'').toString();
37
37
  }
38
38
  else if(thumbnail_config) throw new Error('Unsupported media file name: '+JSON.stringify(thumbnail_config));
39
- fname += media_ext;
39
+ fname += ((thumbnail_config && thumbnail_config.format) ? '.' + thumbnail_config.format : media_ext);
40
40
  return path.join(path.join(module.jsh.Config.datadir,'media'),fname);
41
41
  }
42
42
 
43
- exports.getMediaFile = function(media_file_id, media_filename, media_ext, thumbnail_name, thumbnail_config, callback){
43
+ exports.getMediaFile = function(media_file_id, media_filename, media_ext, thumbnail_id, thumbnail_config, callback){
44
44
  var jsh = module.jsh;
45
45
 
46
46
  var srcpath = funcs.getMediaFilename(media_file_id, media_ext);
47
- var fpath = funcs.getMediaFilename(media_file_id, media_ext, thumbnail_name, thumbnail_config);
47
+ var fpath = funcs.getMediaFilename(media_file_id, media_ext, thumbnail_id, thumbnail_config);
48
48
  var fname = path.basename(media_filename);
49
49
 
50
50
  if(thumbnail_config && thumbnail_config.format){
@@ -54,7 +54,7 @@ module.exports = exports = function(module, funcs){
54
54
 
55
55
  var transformMedia = function(transform_callback){
56
56
  if('resize' in thumbnail_config) jsh.Extensions.image.resize(srcpath, fpath, thumbnail_config.resize, thumbnail_config.format, transform_callback);
57
- else if('crop' in thumbnail_config) jsh.Extensions.image.crop(srcpath, fpath, thumbnail_config.format, transform_callback);
57
+ else if('crop' in thumbnail_config) jsh.Extensions.image.crop(srcpath, fpath, thumbnail_config.crop, thumbnail_config.format, transform_callback);
58
58
  else if('format' in thumbnail_config) jsh.Extensions.image.resample(srcpath, fpath, thumbnail_config.format, transform_callback);
59
59
  else return transform_callback(new Error('Invalid thumbnail_config: '+JSON.stringify(thumbnail_config)));
60
60
  }
@@ -68,16 +68,27 @@ module.exports = exports = function(module, funcs){
68
68
  if(err) return callback(err);
69
69
  fs.stat(fpath, function (err, stat) {
70
70
  if(err) return callback(err);
71
- return callback(null, fpath, stat);
71
+ return callback(null, fpath, fname, stat);
72
72
  });
73
73
  });
74
74
  }
75
75
  else return callback(err);
76
76
  }
77
- else return callback(null, fpath, stat);
77
+ else return callback(null, fpath, fname, stat);
78
78
  });
79
79
  }
80
80
 
81
+ exports.appendThumbnail = function(fpath, thumbnail_id, thumbnail_config){
82
+ if(!fpath) return fpath;
83
+ var fname = path.basename(fpath);
84
+ var fext = path.extname(fname);
85
+ fpath = fpath.substr(0, fpath.length - fext.length) + '.' + thumbnail_id + fext;
86
+ if(thumbnail_config && thumbnail_config.format){
87
+ if(fext.length > 1) fpath = fpath.substr(0, fpath.length - fext.length) + '.' + thumbnail_config.format;
88
+ }
89
+ return fpath;
90
+ }
91
+
81
92
  exports.media = function (req, res, next) {
82
93
  var verb = req.method.toLowerCase();
83
94
 
@@ -103,12 +114,12 @@ module.exports = exports = function(module, funcs){
103
114
  if (verb == 'get'){
104
115
  if(!req.params || !req.params.media_key) return next();
105
116
  var media_key = req.params.media_key;
106
- var thumbnail_name = req.params.thumbnail;
117
+ var thumbnail_id = req.params.thumbnail;
107
118
  var thumbnail_config = null;
108
- if(thumbnail_name) thumbnail_config = siteConfig.media_thumbnails[thumbnail_name];
119
+ if(thumbnail_id) thumbnail_config = siteConfig.media_thumbnails[thumbnail_id];
109
120
 
110
121
  //Invalid Thumbnail
111
- if(thumbnail_name && !thumbnail_config) return next();
122
+ if(thumbnail_id && !thumbnail_config) return next();
112
123
 
113
124
  //Check if media exists
114
125
  sql_ptypes = [dbtypes.BigInt];
@@ -167,11 +178,10 @@ module.exports = exports = function(module, funcs){
167
178
  }
168
179
 
169
180
  var serveoptions = { attachment: !!('download' in Q), mime_override: media.media_ext };
170
- var fname = path.basename(media.media_filename);
171
181
 
172
182
  if(thumbnail_config && thumbnail_config.format) serveoptions.mime_override = '.' + thumbnail_config.format;
173
183
 
174
- funcs.getMediaFile(media.media_file_id, media.media_filename, media.media_ext, thumbnail_name, thumbnail_config, function(err, fpath, stat){
184
+ funcs.getMediaFile(media.media_file_id, media.media_filename, media.media_ext, thumbnail_id, thumbnail_config, function(err, fpath, fname, stat){
175
185
  if(err){
176
186
  if(err.message == 'Media file not found') return Helper.GenError(req, res, -33, 'Media file not found.');
177
187
  jsh.Log.error(err.toString() + '\n' + (err.stack?err.stack:(new Error()).stack));
@@ -183,7 +183,7 @@ module.exports = exports = function(module, funcs){
183
183
 
184
184
  exports.replaceBranchURLs = function(content, options){
185
185
  options = _.extend({
186
- getMediaURL: function(media_key, branchData, getLinkContent, urlparts){ return ''; },
186
+ getMediaURL: function(media_key, thumbnail_id, branchData, getLinkContent, thumbnail, urlparts){ return ''; },
187
187
  getPageURL: function(page_key, branchData, getLinkContent, urlparts){ return ''; },
188
188
  onError: function(err){ },
189
189
  onComponentData: null, //function(content){ return content; },
@@ -221,13 +221,14 @@ module.exports = exports = function(module, funcs){
221
221
  }
222
222
  var urlparts = urlparser.parse(url, true);
223
223
  if(!urlparts.path) return url;
224
- var patharr = (urlparts.path||'').split('/');
224
+ var patharr = (urlparts.pathname||'').split('/');
225
225
 
226
226
  if((urlparts.path.indexOf('/_funcs/media/')==0) && (patharr.length>=4)){
227
227
  var media_key = patharr[3];
228
228
  if(parseInt(media_key).toString()==media_key){
229
229
  try{
230
- var media_url = options.getMediaURL(media_key, options.branchData, getLinkContent, urlparts);
230
+ var thumbnail_id = ((patharr.length >= 5) ? patharr[4] : '');
231
+ var media_url = options.getMediaURL(media_key, thumbnail_id, options.branchData, getLinkContent, urlparts);
231
232
  }
232
233
  catch(ex){
233
234
  if(options.onError) options.onError(ex);
@@ -444,11 +445,11 @@ module.exports = exports = function(module, funcs){
444
445
 
445
446
  function replaceURLs(content, options){
446
447
  var rslt = funcs.replaceBranchURLs(content, _.extend({ replaceComponents: true }, options, {
447
- getMediaURL: function(media_key, branchData, getLinkContent, urlparts){
448
+ getMediaURL: function(media_key, thumbnail_id, branchData, getLinkContent, urlparts){
448
449
  if(!media_files){
449
- return baseurl + urlparts.path.substr(1) + '#@JSHCMS';
450
+ return baseurl + urlparts.pathname.substr(1) + '#@JSHCMS';
450
451
  }
451
- return baseurl+'_funcs/media/'+media_key+'/?media_id='+media_files[media_key].media_id+'#@JSHCMS';
452
+ return baseurl+'_funcs/media/'+media_key+(thumbnail_id?'/'+thumbnail_id:'')+'/?media_id='+media_files[media_key].media_id+'#@JSHCMS';
452
453
  },
453
454
  getPageURL: function(page_key, branchData, getLinkContent, urlparts){
454
455
  if(branch_id) return baseurl+'_funcs/page/'+page_key+'/?branch_id='+branch_id+'&view=1#@JSHCMS';
@@ -1241,6 +1242,7 @@ module.exports = exports = function(module, funcs){
1241
1242
  if(!siteConfig) siteConfig = {};
1242
1243
  var pageSiteConfig = {
1243
1244
  defaultEditorConfig: siteConfig.defaultEditorConfig||{},
1245
+ media_thumbnails: {},
1244
1246
  };
1245
1247
  if(typeof pageSiteConfig.defaultEditorConfig.webSnippetsPath == 'undefined'){
1246
1248
  if(cms.PreviewServer){
@@ -1249,6 +1251,10 @@ module.exports = exports = function(module, funcs){
1249
1251
  pageSiteConfig.defaultEditorConfig.webSnippetsPath = webSnippetPath;
1250
1252
  }
1251
1253
  }
1254
+ _.each(siteConfig.media_thumbnails, function(thumbnail_config, thumbnail_id){
1255
+ if(!thumbnail_config || !thumbnail_config.export) return;
1256
+ pageSiteConfig.media_thumbnails[thumbnail_id] = true;
1257
+ });
1252
1258
  res.end(JSON.stringify({ '_success': 1, siteConfig: pageSiteConfig }));
1253
1259
  });
1254
1260
  });
@@ -120,11 +120,21 @@ module.exports = exports = function(module, funcs){
120
120
  media_keys: {},
121
121
  page_keys: {},
122
122
  results: [],
123
+ site_config: {},
123
124
  };
124
125
  var sql_ptypes = [dbtypes.BigInt];
125
126
  var sql_params = { 'branch_id': branch_id };
126
127
 
127
128
  async.waterfall([
129
+ //Get site_config
130
+ function(search_cb){
131
+ funcs.getSiteConfig(req._DBContext, site_id, { continueOnConfigError: true }, function(err, siteConfig){
132
+ if(err) return search_cb(err);
133
+ searchData.site_config = siteConfig || {};
134
+ return search_cb();
135
+ });
136
+ },
137
+ //Search pages
128
138
  function(search_cb){
129
139
  async.eachOfSeries(cms.BranchItems, function(branch_item, branch_item_type, search_item_cb){
130
140
  if(!branch_item.search) return search_item_cb();
@@ -289,8 +299,9 @@ module.exports = exports = function(module, funcs){
289
299
  return content;
290
300
  }
291
301
  var rslt = funcs.replaceBranchURLs(content, _.extend({ replaceComponents: true }, {
292
- getMediaURL: function(media_key){
302
+ getMediaURL: function(media_key, thumbnail_id){
293
303
  if(!(media_key in searchData.media_keys)) return '';
304
+ if(thumbnail_id) return funcs.appendThumbnail(searchData.media_keys[media_key], thumbnail_id, searchData.site_config.media_thumbnails && searchData.site_config.media_thumbnails[thumbnail_id]);
294
305
  return searchData.media_keys[media_key];
295
306
  },
296
307
  getPageURL: function(page_key){
@@ -25,6 +25,7 @@ var path = require('path');
25
25
  var fs = require('fs');
26
26
  var parse5 = require('parse5');
27
27
  var crypto = require('crypto');
28
+ var urlparser = require('url');
28
29
 
29
30
  module.exports = exports = function(module, funcs){
30
31
  var exports = {};
@@ -1683,6 +1684,110 @@ module.exports = exports = function(module, funcs){
1683
1684
  return htdoc.content;
1684
1685
  }
1685
1686
 
1687
+ exports.applyResponsiveImg = function(content, thumbnails, media_items){
1688
+ if(!content) return content;
1689
+ media_items = media_items || {};
1690
+
1691
+ var responsiveThumbnails = {};
1692
+ if(thumbnails) for(var key in thumbnails){
1693
+ if(thumbnails[key].responsive) responsiveThumbnails[key] = thumbnails[key];
1694
+ }
1695
+
1696
+ if(_.isEmpty(responsiveThumbnails)) return content;
1697
+
1698
+ //If no img tags, return
1699
+ if(content.toLowerCase().indexOf('img') < 0) return content;
1700
+
1701
+ var htdoc = new funcs.HTMLDoc(content);
1702
+ htdoc.applyNodes([
1703
+ { //img tags
1704
+ pred: function(node){ return htdoc.isTag(node, 'img'); },
1705
+ exec: function(node){
1706
+ var src = (htdoc.getAttr(node, 'src')||'').toString();
1707
+ if(!src) return;
1708
+ if(htdoc.hasAttr(node, 'srcset')) return;
1709
+
1710
+ //Check for #@JSHCMS
1711
+ var urlparts = null;
1712
+ try{
1713
+ urlparts = urlparser.parse(src, true);
1714
+ }
1715
+ catch(ex){
1716
+ }
1717
+ if(!urlparts) return;
1718
+
1719
+ if(!Helper.beginsWith(urlparts.hash || '', '#@JSHCMS')) return;
1720
+ if((urlparts.pathname||'').indexOf('/_funcs/media/')!=0) return;
1721
+
1722
+ var patharr = (urlparts.pathname||'').split('/');
1723
+ if(patharr.length < 4) return;
1724
+ var media_key = patharr[3];
1725
+ var media_thumbnail = patharr[4];
1726
+
1727
+ //Check if media_key is valid
1728
+ var media_item = media_items[media_key];
1729
+ var media_width = 0;
1730
+ var media_height = 0;
1731
+ if(media_item){
1732
+ if(media_item.media_width) media_width = media_item.media_width;
1733
+ if(media_item.media_height) media_height = media_item.media_height;
1734
+ }
1735
+ if(media_thumbnail && (media_thumbnail in thumbnails)){
1736
+ var tgtThumbnail = thumbnails[media_thumbnail];
1737
+ if(tgtThumbnail.resize && tgtThumbnail.resize.length){
1738
+ media_width = tgtThumbnail.resize[0];
1739
+ media_height = tgtThumbnail.resize[1];
1740
+ }
1741
+ else if(tgtThumbnail.crop && tgtThumbnail.crop.length){
1742
+ media_width = tgtThumbnail.crop[0];
1743
+ media_height = tgtThumbnail.crop[1];
1744
+ }
1745
+ }
1746
+
1747
+ var srcsets = [];
1748
+ var max_width = media_width;
1749
+ for(var thumbnail_id in responsiveThumbnails){
1750
+ var thumbnail = responsiveThumbnails[thumbnail_id];
1751
+ var srcsetCondition = '';
1752
+ if(_.isString(thumbnail.responsive)) srcsetCondition = thumbnail.responsive;
1753
+ else {
1754
+ var thumbnail_width = 0;
1755
+ if(media_item && media_item.media_width) thumbnail_width = media_item.media_width;
1756
+ if(thumbnail.resize && thumbnail.resize.length) thumbnail_width = thumbnail.resize[0];
1757
+ else if(thumbnail.crop && thumbnail.crop.length) thumbnail_width = thumbnail.crop[0];
1758
+
1759
+ if(thumbnail_width && (!media_width || (thumbnail_width < media_width))){
1760
+ srcsetCondition = thumbnail_width + 'w';
1761
+ if(thumbnail_width >= max_width) max_width = thumbnail_width + 1;
1762
+ }
1763
+ }
1764
+
1765
+ if(srcsetCondition){
1766
+ //Generate srcset
1767
+ var srcsetUrl = urlparts.protocol+'//'+urlparts.host+'/_funcs/media/'+media_key+'/'+thumbnail_id+'/#@JSHCMS'+urlparts.hash.substr(('#@JSHCMS').length);
1768
+ srcsets.push(srcsetUrl+' '+srcsetCondition);
1769
+ }
1770
+ }
1771
+
1772
+ //Update img tag
1773
+ if(srcsets.length){
1774
+ srcsets.push(src + ' ' + max_width + 'w');
1775
+ htdoc.appendAttr(node, 'srcset', srcsets.join(', '), 'srcset');
1776
+ }
1777
+
1778
+ if(media_width && media_height && htdoc.hasAttr(node, 'cms-image-dimensions')){
1779
+ if(!htdoc.hasAttr(node, 'width') && !htdoc.hasAttr(node, 'height')){
1780
+ htdoc.appendAttr(node, 'width', media_width.toString());
1781
+ htdoc.appendAttr(node, 'height', media_height.toString());
1782
+ htdoc.removeAttr(node, 'cms-image-dimensions');
1783
+ }
1784
+ }
1785
+ }
1786
+ },
1787
+ ]);
1788
+ return htdoc.content;
1789
+ }
1790
+
1686
1791
  exports.generateEditorTemplate = function(content, options){
1687
1792
  options = _.extend({ cmsBaseUrl: '/', template_variables: {} }, options);
1688
1793
 
@@ -130,15 +130,6 @@ module.exports = exports = function(module, funcs){
130
130
 
131
131
  async.waterfall([
132
132
 
133
- //Get site config
134
- function(cb){
135
- funcs.getSiteConfig('deployment', branchData.site_id, { }, function(err, siteConfig){
136
- if(err) return cb(err);
137
- branchData.site_config = siteConfig || {};
138
- return cb();
139
- });
140
- },
141
-
142
133
  //Get template_variables for branch
143
134
  function(cb){
144
135
  var sql = "select site_editor deployment_target_id, v_my_site.deployment_target_template_variables, v_my_branch_desc.site_id, deployment_target_publish_config, deployment_target_publish_path from {schema}.v_my_branch_desc left outer join {schema}.v_my_site on v_my_site.site_id = v_my_branch_desc.site_id left outer join {schema}.deployment_target on deployment_target.deployment_target_id = v_my_site.deployment_target_id where v_my_branch_desc.branch_id=@branch_id";
@@ -167,16 +158,22 @@ module.exports = exports = function(module, funcs){
167
158
  return deploy_cb('Error parsing deployment_target_publish_config: '+ex.toString());
168
159
  }
169
160
 
170
- funcs.parseTemplateVariables('publish', 'deployment', branchData.site_id, branchData.site_config, template_variables, deployment_target_publish_config, function(err, parsed_template_variables){
161
+ //Get site config
162
+ funcs.getSiteConfig('deployment', branchData.site_id, { }, function(err, siteConfig){
171
163
  if(err) return cb(err);
172
- template_variables = parsed_template_variables;
173
- branchData.template_variables = template_variables;
164
+ branchData.site_config = siteConfig || {};
165
+
166
+ funcs.parseTemplateVariables('publish', 'deployment', branchData.site_id, branchData.site_config, template_variables, deployment_target_publish_config, function(err, parsed_template_variables){
167
+ if(err) return cb(err);
168
+ template_variables = parsed_template_variables;
169
+ branchData.template_variables = template_variables;
174
170
 
175
- //Deployment Target Publish Params
176
- var publish_params = _.extend(JSON.parse(JSON.stringify(template_variables)), deployment_target_publish_config);
177
- branchData.publish_params = publish_params;
171
+ //Deployment Target Publish Params
172
+ var publish_params = _.extend(JSON.parse(JSON.stringify(template_variables)), deployment_target_publish_config);
173
+ branchData.publish_params = publish_params;
178
174
 
179
- return cb();
175
+ return cb();
176
+ });
180
177
  });
181
178
  });
182
179
  },
@@ -322,8 +319,11 @@ module.exports = exports = function(module, funcs){
322
319
  if(page.compiled.content) for(var key in page.compiled.content) allContent[key + ' content'] = page.compiled.content[key];
323
320
  for(var key in allContent){
324
321
  funcs.replaceBranchURLs(allContent[key], {
325
- getMediaURL: function(media_key, branchData, getLinkContent){
322
+ getMediaURL: function(media_key, thumbnail_id, branchData, getLinkContent){
326
323
  if(!(media_key in branchData.media_keys)) throw new Error('<' + key + '>: Link to missing Media ID #'+media_key.toString()+': ...'+getLinkContent()+'...');
324
+ if(thumbnail_id){
325
+ if(!(branchData.site_config.media_thumbnails && branchData.site_config.media_thumbnails[thumbnail_id])) throw new Error('<' + key + '>: Media #'+media_key.toString()+' links to invalid Thumbnail ID: '+thumbnail_id+' ...'+getLinkContent()+'...');
326
+ }
327
327
  return '';
328
328
  },
329
329
  getPageURL: function(page_key, branchData, getLinkContent){
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsharmony-cms",
3
- "version": "1.3.0",
3
+ "version": "1.5.1",
4
4
  "description": "jsHarmony CMS",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -30,8 +30,8 @@
30
30
  "diff2html": "3.4.1",
31
31
  "ejs": "2.7.4",
32
32
  "js-beautify": "^1.11.0",
33
- "jsharmony": "^1.1.201",
34
- "jsharmony-factory": "^1.1.97",
33
+ "jsharmony": "^1.4.0",
34
+ "jsharmony-factory": "^1.2.0",
35
35
  "lodash": "^4.17.19",
36
36
  "node-forge": "^0.10.0",
37
37
  "parse5": "^6.0.1",
@@ -579,7 +579,9 @@ DataModelTemplate_GridPreview.prototype.buildTemplate = function(componentTempla
579
579
  model.oncommit = '_this.onCommit(xmodel, rowid, callback);';
580
580
  model.ejs = '';
581
581
  model.sort = { [this._sequenceFieldName]: 'asc' };
582
- model.oninit = "jsh.$root('.xform'+xmodel.class).before('<div class=\"dataGridEditor_instructions\"><span style=\"font-size:1.3em;position:relative;top:1px;margin-right:2px;margin-left:4px;\">&#x1f6c8;</span> Add, edit, and re-order items using the icons :: Double-click to edit</div>');";
582
+ model.oninit = [
583
+ "jsh.$root('.xform'+xmodel.class).before('<div class=\"dataGridEditor_instructions\"><span style=\"font-size:1.3em;position:relative;top:1px;margin-right:2px;margin-left:4px;\">&#x1f6c8;</span> Add, edit, and re-order items using the icons <span class=\"dataGridEditor_instructions_doubleClick\">:: Double-click to edit</span></div>');",
584
+ ].join(' ');
583
585
  model.rowclass = "<%=xejs.iif(rowid==0,'first')%>";
584
586
  model.tableclass = ((model.tableclass||'')+' '+(componentConfig&&componentConfig.options&&componentConfig.options.component_preview_size=='collapse'?'jsharmony_cms_component_preview_collapse':'jsharmony_cms_component_preview_expand')).trim();
585
587
  this._jsh.XPage.ParseModelDefinition(model, null, null, { ignoreErrors: true });
@@ -3786,6 +3788,8 @@ TemplateRenderer.render = function(config, type, jsh, cms, componentConfig, addi
3786
3788
  isInEditor: true,
3787
3789
  isInPageEditor: true,
3788
3790
  isInComponentEditor: ((type=='gridRowDataPreview') || (type=='gridItemPreview')),
3791
+ componentRenderClass: 'jsharmony_cms_componentRender_'+jsh.XExt.escapeCSSClass((componentConfig&&componentConfig.id)||'')+'_'+cms.componentManager.getUniqueId().toString(),
3792
+ getMediaThumbnails: function(url){ return cms.componentManager.getMediaThumbnails(url); },
3789
3793
  renderPlaceholder: renderPlaceholder,
3790
3794
  }, additionalRenderParams));
3791
3795
 
@@ -4329,6 +4333,8 @@ exports = module.exports = function(jsh, cms){
4329
4333
  this.containerlessComponents = {};
4330
4334
  this.cntContainerlessComponents = 0;
4331
4335
 
4336
+ var maxUniqueId = 0;
4337
+
4332
4338
  this.load = function(onError){
4333
4339
  _this.loadSystemComponentTemplates(onError);
4334
4340
  }
@@ -4670,6 +4676,8 @@ exports = module.exports = function(jsh, cms){
4670
4676
  isInEditor: true,
4671
4677
  isInPageEditor: false,
4672
4678
  isInComponentEditor: false,
4679
+ componentRenderClass: 'jsharmony_cms_componentRender_'+XExt.escapeCSSClass((component&&component.id)||'')+'_'+cms.componentManager.getUniqueId().toString(),
4680
+ getMediaThumbnails: function(url){ return cms.componentManager.getMediaThumbnails(url); },
4673
4681
  items: data.items,
4674
4682
  item: data.item,
4675
4683
  component: properties,
@@ -4696,6 +4704,26 @@ exports = module.exports = function(jsh, cms){
4696
4704
  }
4697
4705
  return rslt;
4698
4706
  }
4707
+
4708
+ this.getUniqueId = function(){ return ++maxUniqueId; }
4709
+
4710
+ this.getMediaThumbnails = function(url){
4711
+ if(!cms.site_config || !cms.site_config.media_thumbnails) return {};
4712
+ if(!url || (url.indexOf('#@JSHCMS') < 0)) return {};
4713
+
4714
+ var urlparts = document.createElement('a');
4715
+ urlparts.href = url;
4716
+ var patharr = (urlparts.pathname||'').split('/');
4717
+ var rslt = {};
4718
+ if(((urlparts.pathname||'').indexOf('/_funcs/media/')==0) && (patharr.length>=4)){
4719
+ for(var thumbnail_id in cms.site_config.media_thumbnails){
4720
+ if((patharr.length >= 5) && patharr[4]) patharr[4] = thumbnail_id;
4721
+ else patharr.splice(4,0,thumbnail_id);
4722
+ rslt[thumbnail_id] = urlparts.protocol + '//' + urlparts.host + patharr.join('/') + (urlparts.search||'') + (urlparts.hash||'');
4723
+ }
4724
+ }
4725
+ return rslt;
4726
+ }
4699
4727
  }
4700
4728
  },{"./jsHarmonyCMS.Component":21}],23:[function(require,module,exports){
4701
4729
  /*
@@ -6146,6 +6174,9 @@ exports = module.exports = function(jsh, cms, toolbarContainer){
6146
6174
  ],
6147
6175
  contextmenu: 'jsharmonycmscomponentcontextmenu link linkchecker image imagetools table spellchecker configurepermanentpen',
6148
6176
  toolbar: 'formatselect | backcolor forecolor | bold italic underline | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image charmapmaterialicons table fullscreen | jsHarmonyCmsWebSnippet | jsHarmonyCmsComponent | jsHarmonyCmsView | jsHarmonyCmsEndEdit',
6177
+ mobile: {
6178
+ toolbar_mode: 'floating',
6179
+ },
6149
6180
  removed_menuitems: 'newdocument',
6150
6181
  image_advtab: true,
6151
6182
  menu: {
@@ -667,4 +667,28 @@ span[data-component] {
667
667
  right:6px;
668
668
  cursor:pointer;
669
669
  font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;
670
+ }
671
+
672
+ @media screen and (max-width: 500px) {
673
+ .jsharmony_cms .xactions.secondaryform {
674
+ padding-top:20px;
675
+ }
676
+ .jsharmony_cms .xactions.secondarygrid {
677
+ padding-top:14px;
678
+ }
679
+ .jsharmony_cms_dialog .jsharmony_cms_component_dialog > .xbodyhead {
680
+ position:relative;
681
+ }
682
+ .jsharmony_cms_component_dataGridEditor .dataGridEditor_instructions {
683
+ border-right: 2px solid #eee;
684
+ }
685
+ .dataGridEditor_instructions_doubleClick {
686
+ display:none;
687
+ }
688
+ .xdialogbox.jsharmony_cms_component_dialog {
689
+ min-width:auto !important;
690
+ }
691
+ .jsharmony_cms_component_dataGridEditor [data-component-template="gridRow"] .component_toolbar {
692
+ text-align:left;
693
+ }
670
694
  }
@@ -281,6 +281,7 @@ cms-content-editor
281
281
  cms-content-editor-type
282
282
  cms-component-editor-remove-class
283
283
  cms-component-editor-add-class
284
+ cms-image-dimensions
284
285
 
285
286
  ===================
286
287
  EJS Container Slurp
@@ -303,6 +304,7 @@ getEJSOutput(f)
303
304
  getSitemapURL(sitemap_item)
304
305
  getMenuURL(menu_item)
305
306
  renderPlaceholder({ errors: '' })
307
+ getMediaThumbnails(url)
306
308
 
307
309
  * Only available for Export Components
308
310
  addFile(filePath, fileContent)
@@ -318,6 +320,7 @@ baseUrl
318
320
  isInEditor
319
321
  isInPageEditor
320
322
  isInComponentEditor
323
+ componentRenderClass
321
324
  sitemap
322
325
  menu (from cms-menu-tag)
323
326
  data { item: &lt;object&gt;, items: Array&lt;object&gt; }