json-object-editor 0.10.425 → 0.10.431

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/js/joe-full.js CHANGED
@@ -12369,6 +12369,8 @@ Column Count
12369
12369
  this.Header = {}
12370
12370
  this.toggleHelpMenu = function(show,target){
12371
12371
 
12372
+ }
12373
+ this.listUnsavedChanges = function(){
12372
12374
  }
12373
12375
  this.Header.Render = this.renderEditorHeader = function(specs){
12374
12376
  var BM = new Benchmarker();
@@ -14531,6 +14533,7 @@ this.renderHTMLContent = function(specs){
14531
14533
  +fillTemplate(fieldlabel,self.current.object)
14532
14534
  +(required && '*' ||'')
14533
14535
  +self.renderFieldTooltip(prop)
14536
+ +self.renderFieldReloadIcon(prop)
14534
14537
  +'</label>';
14535
14538
 
14536
14539
  //render comment
@@ -14623,7 +14626,15 @@ this.renderHTMLContent = function(specs){
14623
14626
 
14624
14627
  return tooltip_html;
14625
14628
  };
14629
+ this.renderFieldReloadIcon = function(prop){
14630
+ var reloadable = self.propAsFuncOrValue(prop.reloadable);
14631
+ if(!reloadable){return '';}
14632
+ //var comment = ($.type(prop.comment) == "function")?prop.comment():prop.comment;
14633
+ var comment_html = ` <joe-icon class="joe-reload-icon fright" title="Reload Field" onclick="_joe.Fields.rerender('${prop.name}')">⟳</joe-icon>`;
14626
14634
 
14635
+ return comment_html;
14636
+ };
14637
+
14627
14638
  this.selectAndRenderFieldType = function(prop){
14628
14639
  //var joeFieldBenchmarker = new Benchmarker();
14629
14640
 
@@ -15856,7 +15867,8 @@ this.renderHTMLContent = function(specs){
15856
15867
  stripecolor:false,
15857
15868
  bgcolor:false,
15858
15869
  checkbox:false,
15859
- value:''
15870
+ value:'',
15871
+ link:false
15860
15872
  },specs);
15861
15873
  var schemaprop = specs.schemaprop && self.propAsFuncOrValue(specs.schemaprop,item);
15862
15874
  var schema = ((schemaprop && item[schemaprop])||schema||item.itemtype||item.type);
@@ -15867,7 +15879,7 @@ this.renderHTMLContent = function(specs){
15867
15879
 
15868
15880
  var idprop = specs.idprop || (schemaobj && schemaobj.idprop) ||'_id';
15869
15881
  var hasMenu = specs.itemMenu && specs.itemMenu.length;
15870
- var action = specs.action || ' onclick="goJoe(_joe.search(\'${'+idprop+'}\')[0],{schema:\''+schema+'\'})" ';
15882
+ var action = self.propAsFuncOrValue(specs.action,item) || ' onclick="goJoe(_joe.search(\'${'+idprop+'}\')[0],{schema:\''+schema+'\'})" ';
15871
15883
  var nonclickable = self.propAsFuncOrValue(specs.nonclickable,item);
15872
15884
  var clickablelistitem = (!specs.gotoButton && !nonclickable/* && !hasMenu*/);
15873
15885
  //var clickablelistitem = !!clickable && (!specs.gotoButton && !hasMenu);
@@ -15882,6 +15894,7 @@ this.renderHTMLContent = function(specs){
15882
15894
  'data-value="' +encodeURI(JSON.stringify(specs.value))+ '" data-isObject=true'
15883
15895
  :'data-value="' + specs.value + '"';
15884
15896
 
15897
+ var link = specs.link && self.propAsFuncOrValue(specs.link,item);
15885
15898
  var html = fillTemplate(`<joe-list-item schema="${schema}"
15886
15899
  itemId="${item[idprop]}"
15887
15900
  idprop="${idprop}"
@@ -15904,13 +15917,13 @@ this.renderHTMLContent = function(specs){
15904
15917
 
15905
15918
  + self.Render.itemCheckbox(item,schemaobj)
15906
15919
 
15907
- +((specs.link && '<a class="non-link" onclick="__cancelClick();" href="'+specs.link+'">')||'')
15920
+ +((link && '<a class="non-link" onclick="__cancelClick();" href="'+link+'">')||'')
15908
15921
 
15909
15922
  + '<div class="joe-field-item-content '+(nonclickable && 'nonclickable' || '')+'" ' + click + ' >'
15910
15923
  + self.propAsFuncOrValue(contentTemplate+__clearDiv__, item)
15911
15924
  + __clearDiv__+'</div>'
15912
15925
 
15913
- +((specs.link && '</a>')||'')
15926
+ +((link && '</a>')||'')
15914
15927
  +self.Render.stripeColor(item,specs.stripecolor)
15915
15928
  + self._renderExpanderButton(expanderContent, item)
15916
15929
  + (specs.deleteButton && deleteButton || '')
@@ -18685,6 +18698,7 @@ Field Rendering Helpers
18685
18698
  var sortable_index;
18686
18699
  this.Autosave = {
18687
18700
  possibleChanges:true,
18701
+ unsavedChanges:false,
18688
18702
  activate:function(){
18689
18703
  if(!self.specs.autosave){
18690
18704
  return;
@@ -18714,7 +18728,8 @@ Field Rendering Helpers
18714
18728
  var showUnsaved = (hasChanged && self.getMode() == "details");
18715
18729
  document.title =( showUnsaved && '*' ||'')
18716
18730
  +document.title.replace('*','');
18717
- self.overlay.toggleClass('unsaved-changes',showUnsaved)
18731
+ self.overlay.toggleClass('unsaved-changes',showUnsaved);
18732
+ self.Autosave.unsavedChanges = hasChanged;
18718
18733
  },intercnt)
18719
18734
  },
18720
18735
  deactivate:function(){
@@ -18946,7 +18961,7 @@ Field Rendering Helpers
18946
18961
  tocheck.map(field=>{
18947
18962
 
18948
18963
  })
18949
- return (tocheck.length > 0);
18964
+ return (tocheck.length > 0)?tocheck:false;
18950
18965
  }
18951
18966
  if(self.overlay.hasClass('active') && !listMode && !self.current.changesConfirmed && tocheck.length){
18952
18967
 
@@ -18954,7 +18969,7 @@ Field Rendering Helpers
18954
18969
  self.current.changesConfirmed = confirmed;
18955
18970
  return confirmed;
18956
18971
  }
18957
- return true;
18972
+ return tocheck;
18958
18973
  }
18959
18974
  /*-------------------------------------------------------------------->
18960
18975
  J | MESSAGING
@@ -19585,7 +19600,10 @@ Field Rendering Helpers
19585
19600
 
19586
19601
  for (const [field, val] of Object.entries(object)) {
19587
19602
  if (val === undefined || val === null) continue;
19588
- if (field == '_id') continue;
19603
+ if (field == '_id'){
19604
+ flattened[field] = val;
19605
+ continue;
19606
+ }
19589
19607
 
19590
19608
  if (typeof val === 'string' && $c.isCuid(val) && recursive && depth > 0) {
19591
19609
  if (!visited.has(val)) {
@@ -21162,7 +21180,7 @@ logit(intent)
21162
21180
  var users = _joe.Data[(user_dataset || 'user')].where({_id:{$in:ids}});
21163
21181
  var html = '';
21164
21182
  users.map(function(user){
21165
- html += self.SERVER.User.Render.cube(user);
21183
+ html += self.SERVER.User.Render.cube(user,cssclass);
21166
21184
  })
21167
21185
  return html;
21168
21186
  },
@@ -21683,6 +21701,7 @@ logit(intent)
21683
21701
  var e = e || window.event;
21684
21702
  if (e.stopPropagation) e.stopPropagation();
21685
21703
  if (e.preventDefault) e.preventDefault();
21704
+ return false;
21686
21705
  },
21687
21706
  getPossibleValues:function(propertyName,schema,specs){
21688
21707
  //TODO:
package/js/joe.js CHANGED
@@ -1,9 +1,3 @@
1
- /* --------------------------------------------------------
2
- *
3
- * JOE - v1.5.0
4
- * Created by: Corey Hadden
5
- *
6
- * -------------------------------------------------------- */
7
1
  /* --------------------------------------------------------
8
2
  *
9
3
  * JOE - v1.5.0
@@ -1076,6 +1070,8 @@ Column Count
1076
1070
  this.Header = {}
1077
1071
  this.toggleHelpMenu = function(show,target){
1078
1072
 
1073
+ }
1074
+ this.listUnsavedChanges = function(){
1079
1075
  }
1080
1076
  this.Header.Render = this.renderEditorHeader = function(specs){
1081
1077
  var BM = new Benchmarker();
@@ -3238,6 +3234,7 @@ this.renderHTMLContent = function(specs){
3238
3234
  +fillTemplate(fieldlabel,self.current.object)
3239
3235
  +(required && '*' ||'')
3240
3236
  +self.renderFieldTooltip(prop)
3237
+ +self.renderFieldReloadIcon(prop)
3241
3238
  +'</label>';
3242
3239
 
3243
3240
  //render comment
@@ -3330,7 +3327,15 @@ this.renderHTMLContent = function(specs){
3330
3327
 
3331
3328
  return tooltip_html;
3332
3329
  };
3330
+ this.renderFieldReloadIcon = function(prop){
3331
+ var reloadable = self.propAsFuncOrValue(prop.reloadable);
3332
+ if(!reloadable){return '';}
3333
+ //var comment = ($.type(prop.comment) == "function")?prop.comment():prop.comment;
3334
+ var comment_html = ` <joe-icon class="joe-reload-icon fright" title="Reload Field" onclick="_joe.Fields.rerender('${prop.name}')">⟳</joe-icon>`;
3333
3335
 
3336
+ return comment_html;
3337
+ };
3338
+
3334
3339
  this.selectAndRenderFieldType = function(prop){
3335
3340
  //var joeFieldBenchmarker = new Benchmarker();
3336
3341
 
@@ -4563,7 +4568,8 @@ this.renderHTMLContent = function(specs){
4563
4568
  stripecolor:false,
4564
4569
  bgcolor:false,
4565
4570
  checkbox:false,
4566
- value:''
4571
+ value:'',
4572
+ link:false
4567
4573
  },specs);
4568
4574
  var schemaprop = specs.schemaprop && self.propAsFuncOrValue(specs.schemaprop,item);
4569
4575
  var schema = ((schemaprop && item[schemaprop])||schema||item.itemtype||item.type);
@@ -4574,7 +4580,7 @@ this.renderHTMLContent = function(specs){
4574
4580
 
4575
4581
  var idprop = specs.idprop || (schemaobj && schemaobj.idprop) ||'_id';
4576
4582
  var hasMenu = specs.itemMenu && specs.itemMenu.length;
4577
- var action = specs.action || ' onclick="goJoe(_joe.search(\'${'+idprop+'}\')[0],{schema:\''+schema+'\'})" ';
4583
+ var action = self.propAsFuncOrValue(specs.action,item) || ' onclick="goJoe(_joe.search(\'${'+idprop+'}\')[0],{schema:\''+schema+'\'})" ';
4578
4584
  var nonclickable = self.propAsFuncOrValue(specs.nonclickable,item);
4579
4585
  var clickablelistitem = (!specs.gotoButton && !nonclickable/* && !hasMenu*/);
4580
4586
  //var clickablelistitem = !!clickable && (!specs.gotoButton && !hasMenu);
@@ -4589,6 +4595,7 @@ this.renderHTMLContent = function(specs){
4589
4595
  'data-value="' +encodeURI(JSON.stringify(specs.value))+ '" data-isObject=true'
4590
4596
  :'data-value="' + specs.value + '"';
4591
4597
 
4598
+ var link = specs.link && self.propAsFuncOrValue(specs.link,item);
4592
4599
  var html = fillTemplate(`<joe-list-item schema="${schema}"
4593
4600
  itemId="${item[idprop]}"
4594
4601
  idprop="${idprop}"
@@ -4611,13 +4618,13 @@ this.renderHTMLContent = function(specs){
4611
4618
 
4612
4619
  + self.Render.itemCheckbox(item,schemaobj)
4613
4620
 
4614
- +((specs.link && '<a class="non-link" onclick="__cancelClick();" href="'+specs.link+'">')||'')
4621
+ +((link && '<a class="non-link" onclick="__cancelClick();" href="'+link+'">')||'')
4615
4622
 
4616
4623
  + '<div class="joe-field-item-content '+(nonclickable && 'nonclickable' || '')+'" ' + click + ' >'
4617
4624
  + self.propAsFuncOrValue(contentTemplate+__clearDiv__, item)
4618
4625
  + __clearDiv__+'</div>'
4619
4626
 
4620
- +((specs.link && '</a>')||'')
4627
+ +((link && '</a>')||'')
4621
4628
  +self.Render.stripeColor(item,specs.stripecolor)
4622
4629
  + self._renderExpanderButton(expanderContent, item)
4623
4630
  + (specs.deleteButton && deleteButton || '')
@@ -7392,6 +7399,7 @@ Field Rendering Helpers
7392
7399
  var sortable_index;
7393
7400
  this.Autosave = {
7394
7401
  possibleChanges:true,
7402
+ unsavedChanges:false,
7395
7403
  activate:function(){
7396
7404
  if(!self.specs.autosave){
7397
7405
  return;
@@ -7421,7 +7429,8 @@ Field Rendering Helpers
7421
7429
  var showUnsaved = (hasChanged && self.getMode() == "details");
7422
7430
  document.title =( showUnsaved && '*' ||'')
7423
7431
  +document.title.replace('*','');
7424
- self.overlay.toggleClass('unsaved-changes',showUnsaved)
7432
+ self.overlay.toggleClass('unsaved-changes',showUnsaved);
7433
+ self.Autosave.unsavedChanges = hasChanged;
7425
7434
  },intercnt)
7426
7435
  },
7427
7436
  deactivate:function(){
@@ -7653,7 +7662,7 @@ Field Rendering Helpers
7653
7662
  tocheck.map(field=>{
7654
7663
 
7655
7664
  })
7656
- return (tocheck.length > 0);
7665
+ return (tocheck.length > 0)?tocheck:false;
7657
7666
  }
7658
7667
  if(self.overlay.hasClass('active') && !listMode && !self.current.changesConfirmed && tocheck.length){
7659
7668
 
@@ -7661,7 +7670,7 @@ Field Rendering Helpers
7661
7670
  self.current.changesConfirmed = confirmed;
7662
7671
  return confirmed;
7663
7672
  }
7664
- return true;
7673
+ return tocheck;
7665
7674
  }
7666
7675
  /*-------------------------------------------------------------------->
7667
7676
  J | MESSAGING
@@ -8292,7 +8301,10 @@ Field Rendering Helpers
8292
8301
 
8293
8302
  for (const [field, val] of Object.entries(object)) {
8294
8303
  if (val === undefined || val === null) continue;
8295
- if (field == '_id') continue;
8304
+ if (field == '_id'){
8305
+ flattened[field] = val;
8306
+ continue;
8307
+ }
8296
8308
 
8297
8309
  if (typeof val === 'string' && $c.isCuid(val) && recursive && depth > 0) {
8298
8310
  if (!visited.has(val)) {
@@ -9869,7 +9881,7 @@ logit(intent)
9869
9881
  var users = _joe.Data[(user_dataset || 'user')].where({_id:{$in:ids}});
9870
9882
  var html = '';
9871
9883
  users.map(function(user){
9872
- html += self.SERVER.User.Render.cube(user);
9884
+ html += self.SERVER.User.Render.cube(user,cssclass);
9873
9885
  })
9874
9886
  return html;
9875
9887
  },
@@ -10390,6 +10402,7 @@ logit(intent)
10390
10402
  var e = e || window.event;
10391
10403
  if (e.stopPropagation) e.stopPropagation();
10392
10404
  if (e.preventDefault) e.preventDefault();
10405
+ return false;
10393
10406
  },
10394
10407
  getPossibleValues:function(propertyName,schema,specs){
10395
10408
  //TODO:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-object-editor",
3
- "version": "0.10.425",
3
+ "version": "0.10.431",
4
4
  "description": "JOE the Json Object Editor | Platform Edition",
5
5
  "main": "app.js",
6
6
  "scripts": {
@@ -311,8 +311,9 @@
311
311
  JOE.init();
312
312
  //capp.init();
313
313
  var capp_apps = '${USERAPPS}'.split(',') || [];
314
+ var caobj = ${USERAPPSOBJ};
314
315
  var all_apps = '${APPS}'.split(',') || [];
315
- capp.Menu.addFromApps(capp_apps,'<joe-subtext>${this.webconfig.name}</joe-subtext>${APPNAME}');
316
+ capp.Menu.addFromApps(caobj,'<joe-subtext>${this.webconfig.name}</joe-subtext>${APPNAME}');
316
317
  //capp.Button.add('Docs',null,'window.open(\'/JsonObjectEditor/docs.html\')','capp-header');
317
318
 
318
319
 
package/readme.md CHANGED
@@ -198,11 +198,13 @@ a list of schema objects that can configure the editor fields, these can be give
198
198
  alert(obj.name);
199
199
  },*/
200
200
  onblur:logit,
201
+
201
202
  hideNumbers:boolean *toggle list numbers*
202
203
  multipleCallback:function to be called after a multi-edit. passed list of edited items.
203
204
  onUpdate: callback for after update. passed single edited items.
204
205
  onMultipleUpdate:callback for after multi update.passed list of edited items.
205
206
  filters: array of objects
207
+ checkChanges:(bool) whether or not to check for changes via interval and on leave
206
208
  }
207
209
  ##Table View
208
210
  - add tableView object to a schema;
@@ -28,10 +28,10 @@ var apps = function(){
28
28
  var default_schemas = JOE.webconfig.default_schemas;
29
29
  return{
30
30
  joe:{
31
- title:'Platform',
31
+ title:'JOE Platform',
32
32
  info:'This is the master Data Model Management System (DMMS) admin page. It has access to all of your schemas and datasets.',
33
33
  description:'JOE is the Json Object Editor. The Platform app has access to all your schemas and datasets.',
34
- plugins:['auth.js','formBuilder.js','reportbuilder.js','callbackTester.js','memberRegistry.js','awsConnect.js','notifier.js','calendar.js','inventory.js','money.js'],
34
+ plugins:['auth.js','formBuilder.js','reportbuilder.js','callbackTester.js','memberRegistry.js','awsConnect.js','notifier.js','calendar.js','inventory.js','money.js','plugin-utils.js','chatgpt.js','chatgpt-assistants.js','chatgpt-tools.js'],
35
35
  collections:((default_schemas.concat(['schema','group',
36
36
  'site','page','post','layout','block','include','event',
37
37
  'project','board','task',
@@ -68,15 +68,15 @@ var fields = {
68
68
  'coords':{type:'geo'},
69
69
  'state':{type:'select',values:usStates,idprop:'code',template:'${code}',width:'25%',minwidth:'100px',blank:true},
70
70
  'name':{type:'text',onblur:'_joe.TITLE.set()'},
71
- instructions_format:{type:'select',values:['wysiwyg','code'], rerender:'instructions',display:'Instructions Format'},
71
+ instructions_format:{type:'select',values:['wysiwyg','code','text',], rerender:'instructions',display:'Instructions Format'},
72
72
  instructions:{
73
- height:'auto',
73
+ height:'600px',
74
74
  display:'Instructions',
75
75
  type:function(item){
76
- if(!item.instructions_format){
77
- return 'wysiwyg';
76
+ if(!item.instructions_format || item.instructions_format == 'text'){
77
+ return 'rendering';
78
78
  }
79
- if(["code"].indexOf(item.instructions_format) != -1){
79
+ else if(["code"].indexOf(item.instructions_format) != -1){
80
80
  return 'code';
81
81
  }
82
82
  return item.instructions_format;
@@ -86,8 +86,16 @@ var fields = {
86
86
  type: "select",
87
87
  display: "Ai Model",
88
88
  values: [
89
- { value: "gpt-4o", name: "GPT-4o (Current Default)" }
90
- ],
89
+ { value: "gpt-4o", name: "GPT-4o (Fast, 128k)" },
90
+ { value: "gpt-4.1", name: "GPT-4.1 (Strong, 1M)" },
91
+ { value: "gpt-4.1-mini", name: "4.1-mini (Cheap, 1M)" },
92
+ { value: "gpt-4.1-nano", name: "4.1-nano (Fastest, light tasks)" }
93
+ ],
94
+ tooltip:`Ai Model Guide -
95
+ GPT-4o is the default for fast, responsive tasks and supports up to 128k tokens. It’s ideal for short completions, summaries, and dynamic UI tools.
96
+ GPT-4.1 and 4.1-mini support a massive 1 million token context, making them perfect for large inputs like full business profiles, long strategy texts, and multi-object analysis.
97
+ 4.1-mini is significantly cheaper than full 4.1, with great balance for most structured AI workflows.
98
+ 4.1-nano is best for lightweight classification or routing logic where speed and cost matter more than depth.`,
91
99
  default: "gpt-4o",
92
100
  },
93
101
  template:{
package/server/init.js CHANGED
@@ -62,7 +62,7 @@ var app = function(config){
62
62
 
63
63
  });
64
64
  }
65
- ['Apps','Schemas','Mongo','MySQL','Comments'].map(function(m){
65
+ ['Apps','Schemas','Mongo','MySQL','Comments','Utils'].map(function(m){
66
66
  initializeModule(m);
67
67
  });
68
68
  //initializeModule('Apps');
@@ -495,16 +495,54 @@ function Apps(){
495
495
  return app;
496
496
  }
497
497
  this.loadPlugin = function(p){
498
- logit(moduleName+'loading plugin '+p);
498
+ //logit(moduleName+'loading plugin '+p);
499
+ let _joeSrc='';
500
+ console.log(JOE.Utils.color('[plugin]', 'plugin'), 'loaded: '+p);
499
501
  var plugin;
500
502
  if(p.indexOf('.js') == -1){p+= '.js';}
501
- try{
502
- var pluginpath = internalPluginsDir + p;
503
- fs.accessSync(pluginpath, fs.F_OK);
503
+
504
+ const candidateDirs = [
505
+ pluginsDir,
506
+ internalPluginsDir
507
+ ];
508
+ let pluginpath = null;
509
+ let override = false;
510
+ //for (let dir of candidateDirs) {
511
+
512
+
513
+ try {
514
+ let attempt = internalPluginsDir + p;
515
+ fs.accessSync(attempt, fs.F_OK);
516
+ pluginpath = attempt;
504
517
 
505
- }catch(e){
506
- var pluginpath = pluginsDir + p;
507
- }
518
+
519
+ } catch (e) {
520
+ // continue
521
+ }
522
+ try {
523
+ let attempt = pluginsDir + p;
524
+ fs.accessSync(attempt, fs.F_OK);
525
+ if(pluginpath){
526
+ override = true;
527
+ }
528
+ pluginpath = attempt;
529
+
530
+
531
+ } catch (e) {
532
+
533
+ // continue
534
+ }
535
+ // }
536
+ // try{
537
+ // //first try the pluginsDir, then the internalPluginsDir
538
+
539
+
540
+ // var pluginpath = internalPluginsDir + p;
541
+ // fs.accessSync(pluginpath, fs.F_OK);
542
+
543
+ // }catch(e){
544
+ // var pluginpath = pluginsDir + p;
545
+ // }
508
546
  try{
509
547
  try{
510
548
  delete require.cache[require.resolve(pluginpath)];
@@ -512,12 +550,15 @@ function Apps(){
512
550
  console.log(moduleName+'[error] '+p+' not deleted');
513
551
  }
514
552
  plugin = require(pluginpath);
515
-
553
+ _joeSrc = pluginpath;
516
554
  }catch(e){
517
555
  plugin = {error:'plugin '+p+' not found'};
518
556
  console.log(e,plugin);
519
557
  }
558
+ plugin._pathname = _joeSrc;
559
+ plugin._override = override;
520
560
  self.plugins[p.replace('.js','')] = plugin;
561
+
521
562
  return plugin;
522
563
  }
523
564
 
@@ -16,6 +16,9 @@ var os = require('os');
16
16
  const $J = require('./UniversalShorthand');
17
17
  var rd = require('renderizer')({templatesDir:path.resolve(JOE.joedir,JOE.webconfig.templatesDir)});
18
18
 
19
+ function coloredLog(message) {
20
+ console.log(JOE.Utils.color('[server]', 'module'), message);
21
+ }
19
22
  var authBool = JOE.isAuthorized = function(req,res){
20
23
  var users = (JOE.Data && JOE.Data.user) || [];
21
24
 
@@ -140,7 +143,23 @@ server.get(['/API/user/:method'],auth,function(req,res,next){
140
143
  res.jsonp({logout:true});
141
144
  return next();
142
145
  break;
143
-
146
+ case 'apps':
147
+ logit('getting apps for '+req.User.name);
148
+ userApps = [];
149
+ if (req.User.apps && req.User.apps.length) {
150
+ req.User.apps.map(function (app) {
151
+ if (JOE.Apps.cache[app]) {
152
+ userApps.push({
153
+ name: app,
154
+ title: JOE.Apps.cache[app].title,
155
+ description: JOE.Apps.cache[app].description
156
+ });
157
+ }
158
+ });
159
+ }
160
+ res.jsonp({userApps});
161
+ return;//return next();
162
+ break;
144
163
  }
145
164
  }
146
165
 
@@ -711,12 +730,17 @@ server.get(['/JOE/','/JOE/:appname'],auth,function(req,res,next){
711
730
  }
712
731
 
713
732
  //var collections = [];
714
-
733
+ let uappsObj = [];
734
+ USER.apps.map(a=>{
735
+ let aa = Apps[a]||{};
736
+ uappsObj.push({name:a,title:aa.title,description:aa.description});
737
+ });
715
738
  var payload = {
716
739
  APPNAME:currentApp.title || appname,
717
740
  APPINFO:JSON.stringify(stringFunctions(currentApp)),
718
741
  APPS:apps.join(','),
719
742
  USERAPPS:(USER.apps||apps||[]).join(','),
743
+ USERAPPSOBJ:JSON.stringify(uappsObj),
720
744
  webconfig:JOE.webconfig,
721
745
  settings:settings,
722
746
  JOEPATH:JOE.webconfig.joepath,
@@ -761,21 +785,21 @@ server.get(['/RENDER/:contentType/:templateName'],function(req,res){
761
785
 
762
786
  JOE.webDir = webDir = JOE.appDir+'/'+JOE.webconfig.webDir+'/';
763
787
  JOE.Utils.setupFileFolder(webDir,'web');
764
-
788
+ coloredLog('listening on '+JOE.webconfig.port);
765
789
 
766
790
 
767
791
  server.use('/',express.static(JOE.webDir));
768
792
 
769
793
  http.listen(JOE.webconfig.port,function(){
770
794
  //console.log('joe listening on '+JOE.webconfig.port);
771
- console.log(JOE.Utils.color('[server]','module')+' on '+JOE.webconfig.port);
795
+ coloredLog('using webDir: '+JOE.webDir);
772
796
  });
773
797
  JOE.httpServer = http;
774
798
  var httpsPort = JOE.webconfig.httpsPort;
775
799
  if(httpsPort){
776
800
  var httpsServer = https.Server({key: JOE.Pem.serviceKey, cert: JOE.Pem.certificate}, server);
777
801
  httpsServer.listen(httpsPort);
778
- console.log(JOE.Utils.color('[server]','module')+' https on '+httpsPort);
802
+ coloredLog(' https on '+httpsPort);
779
803
  JOE.httpsServer = httpsServer;
780
804
  // pem.createCertificate({selfSigned:true}, function(err, keys){
781
805
  // if(err){
@@ -102,6 +102,9 @@ function Storage(specs){
102
102
 
103
103
  var save_callback = function(err,data){
104
104
  callback(err,data);
105
+ if (!specs || specs.push !== false) {
106
+ JOE.io.emit('item_updated', { results: [data] });
107
+ }
105
108
  if(!err){
106
109
  var ts = new Date().toISOString();
107
110
  var cached = JOE.Cache.findByID(data.itemtype,data._id);
@@ -21,8 +21,7 @@ function UniversalShorthand(){
21
21
  if(items){
22
22
  callback && callback(items);
23
23
  return items;
24
- }
25
-
24
+ }
26
25
  }
27
26
  this.schema = function(schemaname,callback){
28
27
  self._usages.schema++;
@@ -174,6 +174,68 @@ var utils = function(){
174
174
  }
175
175
  return finalItems;
176
176
  }
177
+ this.flattenObject = function(id, options = {}) {
178
+ const { recursive = true, depth = 1, visited = new Set() } = options;
179
+
180
+ let object = null;
181
+
182
+ if (id) {
183
+ object = $J.get(id);
184
+ }
185
+
186
+ if (!object) return null;
187
+
188
+ const flattened = {};
189
+ const currentId = object._id || object.id;
190
+ if (currentId) visited.add(currentId);
191
+
192
+ for (const [field, val] of Object.entries(object)) {
193
+ if (val === undefined || val === null) continue;
194
+ if (field == '_id'){
195
+ flattened[field] = val;
196
+ continue;
197
+ }
198
+ if(field == 'password'){
199
+ flattened[field] = '********';
200
+ continue;
201
+
202
+ }
203
+ if(field =="status"){
204
+ flattened[field] = $J.get(val);
205
+ continue
206
+ }
207
+ if (typeof val === 'string' && $c.isCuid(val) && recursive && depth > 0) {
208
+ if (!visited.has(val)) {
209
+ const expanded = JOE.Utils.flattenObject(val, {
210
+ recursive,
211
+ depth: depth - 1,
212
+ visited: new Set(visited)
213
+ });
214
+ flattened[field] = expanded || val;
215
+ } else {
216
+ flattened[field] = val;
217
+ }
218
+ }
219
+ else if (Array.isArray(val) && val.every(v => typeof v === 'string' && $c.isCuid(v)) && recursive && depth > 0) {
220
+ flattened[field] = val.map(refId => {
221
+ if (!visited.has(refId)) {
222
+ const expanded = JOE.Utils.flattenObject(refId, {
223
+ recursive,
224
+ depth: depth - 1,
225
+ visited: new Set(visited)
226
+ });
227
+ return expanded || refId;
228
+ }
229
+ return refId;
230
+ });
231
+ }
232
+ else {
233
+ flattened[field] = val;
234
+ }
235
+ }
236
+
237
+ return flattened;
238
+ };
177
239
 
178
240
  return this;
179
241
  };