json-object-editor 0.10.650 → 0.10.654

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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /* --------------------------------------------------------
2
2
  *
3
- * json-object-editor - v0.10.636
3
+ * json-object-editor - v0.10.654
4
4
  * Created by: Corey Hadden
5
5
  *
6
6
  * -------------------------------------------------------- */
@@ -5110,9 +5110,145 @@ this.renderHTMLContent = function(specs){
5110
5110
  } else if (elem.msRequestFullscreen) { /* IE11 */
5111
5111
  elem.msRequestFullscreen();
5112
5112
  }
5113
- }
5113
+ };
5114
5114
  encapsulateFieldType('code',self.renderCodeField,
5115
5115
  {fullscreen:this.fullScreenCodeEditor});
5116
+
5117
+ /*----------------------------->
5118
+ Q.1 | JSON Field
5119
+ <-----------------------------*/
5120
+ this.renderJsonField = function(prop){
5121
+ /*|{
5122
+ tags:'render,field,json',
5123
+ specs:'value,standard_field_properties',
5124
+ description:'JSON object editor using Ace in JSON mode; always pretty-prints on load/blur/save.'
5125
+ }|*/
5126
+ var height = (prop.height)?'style="height:'+prop.height+';"' : '';
5127
+ var editor_id = cuid();
5128
+ var rawValue = (typeof prop.value == "undefined") ? {} : prop.value;
5129
+ var displayValue;
5130
+ // Ensure we start from an object and pretty JSON string
5131
+ if (typeof rawValue == "string") {
5132
+ try{
5133
+ rawValue = JSON.parse(rawValue);
5134
+ }catch(e){
5135
+ // fallback: leave as raw string, Ace/validation will handle parse errors on blur/save
5136
+ }
5137
+ }
5138
+ if (rawValue === null || typeof rawValue == "undefined") {
5139
+ rawValue = {};
5140
+ }
5141
+ if (typeof rawValue == "object") {
5142
+ try{
5143
+ displayValue = JSON.stringify(rawValue,null,2);
5144
+ }catch(e){
5145
+ displayValue = "{}";
5146
+ }
5147
+ }else{
5148
+ // Non-object (number/boolean/etc.) – stringify as-is but still require valid JSON
5149
+ try{
5150
+ displayValue = JSON.stringify(rawValue,null,2);
5151
+ }catch(e){
5152
+ displayValue = "{}";
5153
+ }
5154
+ }
5155
+ var html =
5156
+ '<div class="clear joe-ace-holder joe-rendering-field joe-field" '
5157
+ +height+' data-ace_id="'+editor_id+'" data-ftype="ace" name="'+prop.name+'">'+
5158
+ '<textarea class="" id="'+editor_id+'" >'
5159
+ +(displayValue || "{}")+
5160
+ '</textarea>'+
5161
+ '</div>'+
5162
+ '<script>'+
5163
+ 'try{\
5164
+ var editor = ace.edit("'+editor_id+'");\
5165
+ editor.setTheme("ace/theme/tomorrow");\
5166
+ editor.getSession().setUseWrapMode(true);\
5167
+ editor.getSession().setMode("ace/mode/json");\
5168
+ editor.setOptions({\
5169
+ enableBasicAutocompletion: true,\
5170
+ enableLiveAutocompletion: false\
5171
+ });\
5172
+ _joe.ace_editors["'+editor_id+'"] = editor;\
5173
+ }catch(e){ console && console.warn && console.warn("ACE json init error",e);}'
5174
+ +' </script>';
5175
+ return html;
5176
+ };
5177
+ // Helper to (re)parse and pretty-print a json field from its Ace editor.
5178
+ function _normalizeJsonField(fieldName,opts){
5179
+ opts = opts || {};
5180
+ var parent = getJoe(self.joe_index);
5181
+ var $holder = parent.overlay.find('.joe-ace-holder[name="'+fieldName+'"]');
5182
+ if(!$holder.length){ return { ok:true }; }
5183
+ var editorId = $holder.data('ace_id');
5184
+ var editor = _joe.ace_editors[editorId];
5185
+ if(!editor){ return { ok:true }; }
5186
+ var text = editor.getValue();
5187
+ if(!text || !text.trim()){
5188
+ // default to empty object
5189
+ text = '{}';
5190
+ }
5191
+ try{
5192
+ var parsed = JSON.parse(text);
5193
+ // must be an object; if not, still allow but keep as-is
5194
+ try{
5195
+ var pretty = JSON.stringify(parsed,null,2);
5196
+ editor.setValue(pretty,-1);
5197
+ }catch(_e){}
5198
+ $holder.removeClass('joe-field-error');
5199
+ return { ok:true, parsed:parsed };
5200
+ }catch(e){
5201
+ // Attempt a light-weight "loose JSON" fix for common cases:
5202
+ // - Unquoted identifier keys: { name:"Corey" } -> { "name":"Corey" }
5203
+ // - Single-quoted strings: { "name":'Corey' } -> { "name":"Corey" }
5204
+ // - Trailing commas: { "a":1, } -> { "a":1 }
5205
+ var fixed = text;
5206
+ try{
5207
+ // Quote bare identifier keys following { or , and before :
5208
+ fixed = fixed.replace(/([{\[],\s*|[{]\s*|,\s*)([A-Za-z_][A-Za-z0-9_]*)\s*:/g,function(m,prefix,key){
5209
+ return prefix+'"'+key+'":';
5210
+ });
5211
+ // Convert single-quoted strings to double-quoted strings
5212
+ fixed = fixed.replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g,'"$1"');
5213
+ // Remove trailing commas before } or ]
5214
+ fixed = fixed.replace(/,\s*([}\]])/g,'$1');
5215
+
5216
+ var reparsed = JSON.parse(fixed);
5217
+ try{
5218
+ var prettyFixed = JSON.stringify(reparsed,null,2);
5219
+ editor.setValue(prettyFixed,-1);
5220
+ }catch(_e2){}
5221
+ $holder.removeClass('joe-field-error');
5222
+ return { ok:true, parsed:reparsed };
5223
+ }catch(_fixErr){
5224
+ $holder.addClass('joe-field-error');
5225
+ if(opts.showMessage && parent.showMessage){
5226
+ parent.showMessage("Field '"+fieldName+"' must be valid JSON (object).");
5227
+ }
5228
+ return { ok:false, error:e };
5229
+ }
5230
+ }
5231
+ }
5232
+ encapsulateFieldType('json',self.renderJsonField,{
5233
+ ready:function(){
5234
+ // Attach blur handlers to normalize/validate json fields when panel shows
5235
+ var parent = getJoe(self.joe_index);
5236
+ parent.overlay.find('.joe-ace-holder[name]').each(function(){
5237
+ var name = $(this).attr('name');
5238
+ var field = self.getField(name);
5239
+ if(!field){ return true; }
5240
+ var ftype = (self.propAsFuncOrValue(field.type)||'').toLowerCase();
5241
+ if(ftype != 'json'){ return true; }
5242
+ var editorId = $(this).data('ace_id');
5243
+ var editor = _joe.ace_editors[editorId];
5244
+ if(!editor){ return true; }
5245
+ editor.on('blur',function(){
5246
+ _normalizeJsonField(name,{showMessage:false});
5247
+ });
5248
+ });
5249
+ },
5250
+ normalize:_normalizeJsonField
5251
+ });
5116
5252
  /*----------------------------->
5117
5253
  Q.2 | QR Field
5118
5254
  <-----------------------------*/
@@ -5221,7 +5357,7 @@ this.renderHTMLContent = function(specs){
5221
5357
  '<div class="joe-uploader-preview">'+dz_message+'</div>'+
5222
5358
  '</div>'+
5223
5359
  '<div class="joe-uploader-message">add a file</div>'+
5224
- '<div class="joe-button joe-green-button joe-upload-cofirm-button hidden" onclick="_joe.uploaderConfirm(\''+uploader_id+'\',\''+prop.name+'\');">Upload File</div>'+__clearDiv__+
5360
+ '<div class="joe-button joe-green-button joe-upload-cofirm-button ui-helper-hidden hidden" onclick="_joe.uploaderConfirm(\''+uploader_id+'\',\''+prop.name+'\');">Upload File</div>'+__clearDiv__+
5225
5361
  '</div>'
5226
5362
  ;
5227
5363
  //var idprop = prop.idprop || '_id';
@@ -5248,7 +5384,7 @@ this.renderHTMLContent = function(specs){
5248
5384
  var jup_template,html= '';
5249
5385
  var alink ="<a href='${url}${base64}' class='file-link' target='_blank'></a>";
5250
5386
  var delete_btn = '<joe-uploader-file-delete-btn class="svg-shadow" onclick="_joe.Fields.uploader.remove(\''+cuid+'\',\'${filename}\');">'+self.SVG.icon.close+'</joe-uploader-delete-btn>';
5251
- var label = '<joe-uploader-file-label>${filename}</joe-uploader-file-label>';
5387
+ var label = '';
5252
5388
 
5253
5389
 
5254
5390
  files.map(function(file){
@@ -5273,6 +5409,12 @@ this.renderHTMLContent = function(specs){
5273
5409
 
5274
5410
 
5275
5411
 
5412
+ // Build the label with filename and optional OpenAI file id on a new line
5413
+ label = '<joe-uploader-file-label>'+(file.filename || '')+'</joe-uploader-file-label>';
5414
+ if(file.openai_file_id){
5415
+ label += '<joe-uploader-file-oaid >'+file.openai_file_id+'</joe-uploader-file-oaid>';
5416
+ }
5417
+
5276
5418
  if((file.type && file.type.contains('image')) || (!file.type && (file.url||file.base64).split('.').contains(['jpg','jpeg','png','gif','svg']))){
5277
5419
  jup_template = '<joe-uploader-file class="'+(file.uploaded && 'uploaded'||'')+'" style="background-image:url(${url}${base64})">'+alink+label+filesize+delete_btn+'</joe-uploader-file>';
5278
5420
  }else if (docTypes.includes((file.url || file.base64).split('.').pop())) {
@@ -5284,7 +5426,16 @@ this.renderHTMLContent = function(specs){
5284
5426
  '<joe-uploader-file-extension >.'+(file.type.split('/')[1] || '???')+'</joe-uploader-file-extension>'+
5285
5427
  alink+label+filesize+delete_btn+'</joe-uploader-file>';
5286
5428
  }
5287
- html += fillTemplate(jup_template,file);
5429
+ // Build optional OpenAI retry/upload button
5430
+ var openaiBtn = '';
5431
+ var needRetry = (!file.openai_file_id || file.openai_status == 'error');
5432
+ var hasUrl = !!file.url;
5433
+ if(hasUrl && needRetry){
5434
+ var openaiLabel = (file.openai_status == 'error') ? 'Retry OpenAI' : 'Upload to OpenAI';
5435
+ var safeFilename = (file.filename || '').replace(/'/g,"\\'");
5436
+ openaiBtn = '<joe-uploader-openai-btn class="joe-button" style="margin-left:6px;" onclick="_joe.SERVER.Plugins.openaiRetryFromUrl(\''+cuid+'\',\''+safeFilename+'\');">'+openaiLabel+'</joe-uploader-openai-btn>';
5437
+ }
5438
+ html += fillTemplate(jup_template,file) + openaiBtn;
5288
5439
  })
5289
5440
  return html+'<div class="clear"></div>';
5290
5441
  }
@@ -5301,7 +5452,7 @@ this.renderHTMLContent = function(specs){
5301
5452
  $(joe_uploader.dropzone).find('img,div').replaceWith('<img src="' + base64 + '">')
5302
5453
  //joe_uploader.dropzone.html('<img src="' + base64 + '">');
5303
5454
  joe_uploader.message.html('<b>'+file.name+'</b> selected');
5304
- joe_uploader.confirmBtn.removeClass('hidden');
5455
+ joe_uploader.confirmBtn.removeClass('ui-helper-hidden hidden');
5305
5456
  }else {
5306
5457
  results.innerHTML = 'Nothing to upload.';
5307
5458
  }
@@ -5332,7 +5483,7 @@ this.renderHTMLContent = function(specs){
5332
5483
  }
5333
5484
  joe_uploader.files.push(temp_file);
5334
5485
  $(joe_uploader.preview).html(_renderUploaderFilePreviews(joe_uploader.files,joe_uploader.cuid));
5335
- joe_uploader.confirmBtn.removeClass('hidden');
5486
+ joe_uploader.confirmBtn.removeClass('ui-helper-hidden hidden');
5336
5487
  }else {
5337
5488
  results.innerHTML = 'Nothing to upload.';
5338
5489
  }
@@ -5400,6 +5551,20 @@ this.renderHTMLContent = function(specs){
5400
5551
  var fileobj = joe_uploader.files.where({filename:filename})[0];
5401
5552
  fileobj.uploaded = new Date();
5402
5553
  fileobj.url = url;
5554
+ // Apply captured OpenAI info if present
5555
+ var key = self.current.object._id + '/' + filename;
5556
+ var oi = _joe._lastOpenAI && _joe._lastOpenAI[key];
5557
+ fileobj.openai_purpose = 'assistants';
5558
+ if(oi){
5559
+ fileobj.openai_file_id = oi.openai_file_id || null;
5560
+ fileobj.openai_status = (oi.openai_file_id && 'ok') || (oi.openai_error && 'error') || 'not_uploaded';
5561
+ fileobj.openai_error = oi.openai_error || null;
5562
+ if(oi.openai_error){
5563
+ joe_uploader.message.append('<div>OpenAI error: '+oi.openai_error+'</div>');
5564
+ }
5565
+ }else{
5566
+ fileobj.openai_status = fileobj.openai_status || 'not_uploaded';
5567
+ }
5403
5568
  delete fileobj.base64;
5404
5569
  }
5405
5570
 
@@ -5470,6 +5635,14 @@ this.renderHTMLContent = function(specs){
5470
5635
  self.uploaders[id].dropzone = $(this).find('.joe-uploader-dropzone');
5471
5636
  self.uploaders[id].confirmBtn = $(this).find('.joe-upload-cofirm-button');
5472
5637
 
5638
+ // If there are pending files (not uploaded or missing url), show the confirm button
5639
+ var pending = (self.uploaders[id].files || []).some(function(file){
5640
+ return $c.isObject(file) && (!file.uploaded || !file.url);
5641
+ });
5642
+ if(pending){
5643
+ self.uploaders[id].confirmBtn.removeClass('ui-helper-hidden hidden');
5644
+ }
5645
+
5473
5646
  });
5474
5647
  };
5475
5648
  encapsulateFieldType('uploader',self.renderUploaderField,
@@ -7563,6 +7736,11 @@ Field Rendering Helpers
7563
7736
  //ready objectlist
7564
7737
  self.Fields.objectlist.ready();
7565
7738
 
7739
+ //ready json (Ace-based) fields
7740
+ if(self.Fields.json && self.Fields.json.ready){
7741
+ self.Fields.json.ready();
7742
+ }
7743
+
7566
7744
 
7567
7745
  //sidebars
7568
7746
  if(self.current.sidebars){
@@ -7688,10 +7866,26 @@ Field Rendering Helpers
7688
7866
  var f,test_info;
7689
7867
  for(var c in changes){
7690
7868
  f = self.getField(c);
7691
-
7692
-
7693
7869
  if(c[0] != '$' && c != 'joeUpdated' && f && f.type !='content' && !parseBoolean(f.passthrough)){
7694
- if(JSON.stringify(_joe.current.object[c]) == JSON.stringify(cc_construct[c])){
7870
+ // Special handling for json fields: compare parsed objects so
7871
+ // whitespace/formatting-only changes are ignored.
7872
+ var ftype = (self.propAsFuncOrValue(f.type)||'').toLowerCase();
7873
+ if(ftype == 'json'){
7874
+ var orig = _joe.current.object[c];
7875
+ var rawNew = cc_construct[c];
7876
+ var sameJson = false;
7877
+ try{
7878
+ var parsedOrig = (typeof orig == "string") ? JSON.parse(orig) : orig;
7879
+ var parsedNew = (typeof rawNew == "string") ? JSON.parse(rawNew) : rawNew;
7880
+ sameJson = (JSON.stringify(parsedOrig) == JSON.stringify(parsedNew));
7881
+ }catch(_e){
7882
+ // parse failure means it is definitely a change that needs attention
7883
+ sameJson = false;
7884
+ }
7885
+ if(sameJson){
7886
+ continue;
7887
+ }
7888
+ }else if(JSON.stringify(_joe.current.object[c]) == JSON.stringify(cc_construct[c])){
7695
7889
  continue;
7696
7890
  }
7697
7891
  if(['qrcode'].indexOf(f.type) != -1){
@@ -7994,6 +8188,27 @@ Field Rendering Helpers
7994
8188
  }
7995
8189
  var callback = callback || self.current.callback || (self.current.schema && self.current.schema.callback) || defaultCallback; //logit;
7996
8190
  var newObj = self.constructObjectFromFields(self.joe_index);
8191
+ // Normalize json fields: parse to objects and block save on invalid JSON.
8192
+ if(_joe && _joe.current && _joe.current.fields){
8193
+ var jsonField, ftype, normResult;
8194
+ for(var i=0, tot=_joe.current.fields.length; i<tot; i++){
8195
+ jsonField = _joe.current.fields[i];
8196
+ ftype = (self.propAsFuncOrValue(jsonField.type)||'').toLowerCase();
8197
+ if(ftype != 'json'){ continue; }
8198
+ // Ensure editor content is normalized and valid
8199
+ normResult = self.Fields.json && self.Fields.json.normalize
8200
+ ? self.Fields.json.normalize(jsonField.name,{showMessage:true})
8201
+ : { ok:true };
8202
+ if(!normResult.ok){
8203
+ // invalid JSON – do not proceed with save
8204
+ return false;
8205
+ }
8206
+ // Use parsed object if available; default to {} when missing
8207
+ var parsed = (normResult.parsed || (newObj[jsonField.name] && typeof newObj[jsonField.name] == "object"
8208
+ ? newObj[jsonField.name] : {}));
8209
+ newObj[jsonField.name] = parsed;
8210
+ }
8211
+ }
7997
8212
  newObj.joeUpdated = new Date();
7998
8213
  overwrites = overwrites || {};
7999
8214
  var obj = $.extend(newObj,overwrites);
@@ -9764,6 +9979,13 @@ logit(intent)
9764
9979
  url = prefix+response.Key;
9765
9980
  }
9766
9981
  var filename = response.Key.replace(self.current.object._id+'/','');
9982
+ // Stash OpenAI info for use during finalization
9983
+ _joe._lastOpenAI = _joe._lastOpenAI || {};
9984
+ _joe._lastOpenAI[response.Key] = {
9985
+ openai_file_id: response.openai_file_id,
9986
+ openai_purpose: response.openai_purpose,
9987
+ openai_error: response.openai_error
9988
+ };
9767
9989
  if(field.url_field){
9768
9990
  var nprop = {};
9769
9991
  _joe.current.object[field.url_field] = url;
@@ -9788,6 +10010,53 @@ logit(intent)
9788
10010
  file: { base64:data.base64, extension:data.extension, type:data.contentType, filename: (_joe.current.object._id+data.extension), name: (_joe.current.object._id+data.extension) },
9789
10011
  field: data.field
9790
10012
  }, callback);
10013
+ },
10014
+ // Retry uploading a specific file to OpenAI using its URL.
10015
+ // Invoked by the uploader row action.
10016
+ openaiRetryFromUrl:function(uploader_id, filename){
10017
+ try{
10018
+ var joe_uploader = self.uploaders[uploader_id];
10019
+ if(!joe_uploader){ return alert('Uploader not found'); }
10020
+ var file = joe_uploader.files.where({filename:filename})[0];
10021
+ if(!file){ return alert('File not found'); }
10022
+ if(!file.url){ return alert('No URL to upload to OpenAI'); }
10023
+ var btn = $(joe_uploader.preview).find("joe-uploader-openai-btn:contains('"+filename+"')");
10024
+ joe_uploader.message.append('<div>Uploading to OpenAI...</div>');
10025
+ $.ajax({
10026
+ url: (location.port && '//'+__jsc.hostname+':'+__jsc.port+'/API/plugin/chatgpt/filesRetryFromUrl/') || '//'+__jsc.hostname+'/API/plugin/chatgpt/filesRetryFromUrl/',
10027
+ type:'POST',
10028
+ data:{
10029
+ url:file.url,
10030
+ filename:file.filename,
10031
+ contentType:file.type
10032
+ },
10033
+ success:function(resp){
10034
+ if(resp && resp.success && resp.openai_file_id){
10035
+ file.openai_file_id = resp.openai_file_id;
10036
+ file.openai_purpose = resp.openai_purpose || 'assistants';
10037
+ file.openai_status = 'ok';
10038
+ file.openai_error = null;
10039
+ joe_uploader.message.append('<div>OpenAI file attached</div>');
10040
+ }else{
10041
+ file.openai_status = 'error';
10042
+ file.openai_error = (resp && resp.error) || 'Upload failed';
10043
+ joe_uploader.message.append('<div>OpenAI error: '+(file.openai_error)+'</div>');
10044
+ }
10045
+ // Refresh preview to reflect new status/button
10046
+ $(joe_uploader.preview).html(_renderUploaderFilePreviews(joe_uploader.files,joe_uploader.cuid));
10047
+ // Save object so file metadata is persisted
10048
+ self.updateObject(null, null, true);
10049
+ },
10050
+ error:function(xhr,status,err){
10051
+ var message = (xhr && (xhr.responseJSON && (xhr.responseJSON.error || xhr.responseJSON.code) || xhr.responseText)) || err || status || 'Retry failed';
10052
+ file.openai_status = 'error';
10053
+ file.openai_error = message;
10054
+ joe_uploader.message.append('<div>OpenAI error: '+message+'</div>');
10055
+ }
10056
+ });
10057
+ }catch(e){
10058
+ alert(e && e.message || e);
10059
+ }
9791
10060
  }
9792
10061
  },
9793
10062
  ready:{
@@ -10685,7 +10954,9 @@ logit(intent)
10685
10954
  /*-------------------------------------------------------------------->
10686
10955
  //Colors
10687
10956
  <--------------------------------------------------------------------*/
10688
- this.Colors={}
10957
+ this.Colors={
10958
+ "ai":"#0D9488"
10959
+ }
10689
10960
  this.Colors.priority = {
10690
10961
  1:'#cc4500',
10691
10962
  2:'#FFee33',