json-object-editor 0.10.521 → 0.10.622
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/CHANGELOG.md +14 -0
- package/_www/mcp-nav.js +1 -0
- package/app.js +26 -2
- package/css/joe-styles.css +5 -0
- package/css/joe.css +23 -18
- package/css/joe.min.css +4 -4
- package/js/JsonObjectEditor.jquery.craydent.js +52 -61
- package/js/joe-ai.js +399 -0
- package/js/joe.js +54 -61
- package/js/joe.min.js +1 -1
- package/package.json +5 -5
- package/readme.md +139 -2
- package/server/app-config.js +2 -1
- package/server/fields/core.js +35 -17
- package/server/modules/Server.js +50 -0
- package/server/modules/Storage.js +7 -1
- package/server/modules/Utils.js +2 -1
- package/server/plugins/awsConnect.js +51 -20
- package/server/plugins/chatgpt-assistants.js +3 -3
- package/server/plugins/chatgpt-responses.js +45 -0
- package/server/plugins/chatgpt.js +729 -0
- package/server/plugins/notifier.js +122 -8
- package/server/schemas/ai_assistant.js +255 -0
- package/server/schemas/ai_conversation.js +34 -0
- package/server/schemas/ai_prompt.js +323 -0
- package/server/schemas/ai_response.js +217 -0
- package/server/schemas/ai_tool.js +30 -0
- package/server/schemas/ai_widget_conversation.js +110 -0
- package/server/schemas/notification.js +48 -0
- package/server/schemas/task.js +5 -0
- package/server/webconfig.js +3 -1
package/js/joe-ai.js
CHANGED
|
@@ -408,6 +408,288 @@
|
|
|
408
408
|
}
|
|
409
409
|
customElements.define('joe-object', JoeObject);
|
|
410
410
|
|
|
411
|
+
// ---------- Joe AI Widget: embeddable chat for any site ----------
|
|
412
|
+
class JoeAIWidget extends HTMLElement {
|
|
413
|
+
constructor() {
|
|
414
|
+
super();
|
|
415
|
+
this.attachShadow({ mode: 'open' });
|
|
416
|
+
this.endpoint = this.getAttribute('endpoint') || ''; // base URL to JOE; default same origin
|
|
417
|
+
this.conversation_id = this.getAttribute('conversation_id') || null;
|
|
418
|
+
this.assistant_id = this.getAttribute('assistant_id') || null;
|
|
419
|
+
this.ai_assistant_id = this.getAttribute('ai_assistant_id') || null;
|
|
420
|
+
this.model = this.getAttribute('model') || null;
|
|
421
|
+
this.messages = [];
|
|
422
|
+
this._ui = {};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
connectedCallback() {
|
|
426
|
+
this.renderShell();
|
|
427
|
+
if (this.conversation_id) {
|
|
428
|
+
this.loadHistory();
|
|
429
|
+
} else {
|
|
430
|
+
this.startConversation();
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
get apiBase() {
|
|
435
|
+
return this.endpoint || '';
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
renderShell() {
|
|
439
|
+
const style = document.createElement('style');
|
|
440
|
+
style.textContent = `
|
|
441
|
+
:host {
|
|
442
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
443
|
+
display: block;
|
|
444
|
+
max-width: 420px;
|
|
445
|
+
border: 1px solid #ddd;
|
|
446
|
+
border-radius: 8px;
|
|
447
|
+
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
|
|
448
|
+
overflow: hidden;
|
|
449
|
+
background: #fff;
|
|
450
|
+
}
|
|
451
|
+
.wrapper {
|
|
452
|
+
display: flex;
|
|
453
|
+
flex-direction: column;
|
|
454
|
+
height: 100%;
|
|
455
|
+
}
|
|
456
|
+
.header {
|
|
457
|
+
padding: 8px 12px;
|
|
458
|
+
background: #1f2933;
|
|
459
|
+
color: #f9fafb;
|
|
460
|
+
font-size: 13px;
|
|
461
|
+
display: flex;
|
|
462
|
+
align-items: center;
|
|
463
|
+
justify-content: space-between;
|
|
464
|
+
}
|
|
465
|
+
.title {
|
|
466
|
+
font-weight: 600;
|
|
467
|
+
}
|
|
468
|
+
.status {
|
|
469
|
+
font-size: 11px;
|
|
470
|
+
opacity: 0.8;
|
|
471
|
+
}
|
|
472
|
+
.messages {
|
|
473
|
+
padding: 10px;
|
|
474
|
+
height: 260px;
|
|
475
|
+
overflow-y: auto;
|
|
476
|
+
background: #f5f7fa;
|
|
477
|
+
font-size: 13px;
|
|
478
|
+
}
|
|
479
|
+
.msg {
|
|
480
|
+
margin-bottom: 8px;
|
|
481
|
+
max-width: 90%;
|
|
482
|
+
clear: both;
|
|
483
|
+
}
|
|
484
|
+
.msg.user {
|
|
485
|
+
text-align: right;
|
|
486
|
+
margin-left: auto;
|
|
487
|
+
}
|
|
488
|
+
.bubble {
|
|
489
|
+
display: inline-block;
|
|
490
|
+
padding: 6px 8px;
|
|
491
|
+
border-radius: 10px;
|
|
492
|
+
line-height: 1.4;
|
|
493
|
+
}
|
|
494
|
+
.user .bubble {
|
|
495
|
+
background: #2563eb;
|
|
496
|
+
color: #fff;
|
|
497
|
+
}
|
|
498
|
+
.assistant .bubble {
|
|
499
|
+
background: #e5e7eb;
|
|
500
|
+
color: #111827;
|
|
501
|
+
}
|
|
502
|
+
.footer {
|
|
503
|
+
border-top: 1px solid #e5e7eb;
|
|
504
|
+
padding: 6px;
|
|
505
|
+
display: flex;
|
|
506
|
+
gap: 6px;
|
|
507
|
+
align-items: center;
|
|
508
|
+
}
|
|
509
|
+
textarea {
|
|
510
|
+
flex: 1;
|
|
511
|
+
resize: none;
|
|
512
|
+
border-radius: 6px;
|
|
513
|
+
border: 1px solid #d1d5db;
|
|
514
|
+
padding: 6px 8px;
|
|
515
|
+
font-size: 13px;
|
|
516
|
+
min-height: 34px;
|
|
517
|
+
max-height: 80px;
|
|
518
|
+
}
|
|
519
|
+
button {
|
|
520
|
+
border-radius: 6px;
|
|
521
|
+
border: none;
|
|
522
|
+
background: #2563eb;
|
|
523
|
+
color: #fff;
|
|
524
|
+
padding: 6px 10px;
|
|
525
|
+
font-size: 13px;
|
|
526
|
+
cursor: pointer;
|
|
527
|
+
white-space: nowrap;
|
|
528
|
+
}
|
|
529
|
+
button:disabled {
|
|
530
|
+
opacity: 0.6;
|
|
531
|
+
cursor: default;
|
|
532
|
+
}
|
|
533
|
+
`;
|
|
534
|
+
|
|
535
|
+
const wrapper = document.createElement('div');
|
|
536
|
+
wrapper.className = 'wrapper';
|
|
537
|
+
wrapper.innerHTML = `
|
|
538
|
+
<div class="header">
|
|
539
|
+
<div class="title">${this.getAttribute('title') || 'AI Assistant'}</div>
|
|
540
|
+
<div class="status" id="status">connecting…</div>
|
|
541
|
+
</div>
|
|
542
|
+
<div class="messages" id="messages"></div>
|
|
543
|
+
<div class="footer">
|
|
544
|
+
<textarea id="input" placeholder="${this.getAttribute('placeholder') || 'Ask me anything…'}"></textarea>
|
|
545
|
+
<button id="send">Send</button>
|
|
546
|
+
</div>
|
|
547
|
+
`;
|
|
548
|
+
|
|
549
|
+
this.shadowRoot.innerHTML = '';
|
|
550
|
+
this.shadowRoot.appendChild(style);
|
|
551
|
+
this.shadowRoot.appendChild(wrapper);
|
|
552
|
+
|
|
553
|
+
this._ui.messages = this.shadowRoot.getElementById('messages');
|
|
554
|
+
this._ui.status = this.shadowRoot.getElementById('status');
|
|
555
|
+
this._ui.input = this.shadowRoot.getElementById('input');
|
|
556
|
+
this._ui.send = this.shadowRoot.getElementById('send');
|
|
557
|
+
|
|
558
|
+
this._ui.send.addEventListener('click', () => this.sendMessage());
|
|
559
|
+
this._ui.input.addEventListener('keydown', (e) => {
|
|
560
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
561
|
+
e.preventDefault();
|
|
562
|
+
this.sendMessage();
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
setStatus(text) {
|
|
568
|
+
if (this._ui.status) this._ui.status.textContent = text;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
renderMessages() {
|
|
572
|
+
if (!this._ui.messages) return;
|
|
573
|
+
this._ui.messages.innerHTML = (this.messages || []).map(m => {
|
|
574
|
+
const role = m.role || 'assistant';
|
|
575
|
+
const safe = (m.content || '').replace(/</g, '<').replace(/>/g, '>');
|
|
576
|
+
return `<div class="msg ${role}"><div class="bubble">${safe}</div></div>`;
|
|
577
|
+
}).join('');
|
|
578
|
+
this._ui.messages.scrollTop = this._ui.messages.scrollHeight;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async startConversation() {
|
|
582
|
+
try {
|
|
583
|
+
this.setStatus('connecting…');
|
|
584
|
+
const payload = {
|
|
585
|
+
model: this.model || undefined,
|
|
586
|
+
ai_assistant_id: this.getAttribute('ai_assistant_id') || undefined,
|
|
587
|
+
source: this.getAttribute('source') || 'widget'
|
|
588
|
+
};
|
|
589
|
+
const resp = await fetch(this.apiBase + '/API/plugin/chatgpt/widgetStart', {
|
|
590
|
+
method: 'POST',
|
|
591
|
+
headers: { 'Content-Type': 'application/json' },
|
|
592
|
+
body: JSON.stringify(payload)
|
|
593
|
+
}).then(r => r.json());
|
|
594
|
+
|
|
595
|
+
if (!resp || resp.success !== true) {
|
|
596
|
+
const msg = (resp && resp.error) || 'Failed to start conversation';
|
|
597
|
+
this.setStatus('error: ' + msg);
|
|
598
|
+
this.messages.push({ role:'assistant', content:'[Error starting conversation: '+msg+']', created_at:new Date().toISOString() });
|
|
599
|
+
this.renderMessages();
|
|
600
|
+
console.error('widgetStart error', resp);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
this.conversation_id = resp.conversation_id;
|
|
604
|
+
this.setAttribute('conversation_id', this.conversation_id);
|
|
605
|
+
this.assistant_id = resp.assistant_id || this.assistant_id;
|
|
606
|
+
this.model = resp.model || this.model;
|
|
607
|
+
this.messages = [];
|
|
608
|
+
this.renderMessages();
|
|
609
|
+
this.setStatus('online');
|
|
610
|
+
} catch (e) {
|
|
611
|
+
console.error('widgetStart exception', e);
|
|
612
|
+
this.setStatus('error');
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async loadHistory() {
|
|
617
|
+
try {
|
|
618
|
+
this.setStatus('loading…');
|
|
619
|
+
const resp = await fetch(this.apiBase + '/API/plugin/chatgpt/widgetHistory?conversation_id=' + encodeURIComponent(this.conversation_id))
|
|
620
|
+
.then(r => r.json());
|
|
621
|
+
if (!resp || resp.success !== true) {
|
|
622
|
+
this.setStatus('online');
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
this.assistant_id = resp.assistant_id || this.assistant_id;
|
|
626
|
+
this.model = resp.model || this.model;
|
|
627
|
+
this.messages = resp.messages || [];
|
|
628
|
+
this.renderMessages();
|
|
629
|
+
this.setStatus('online');
|
|
630
|
+
} catch (e) {
|
|
631
|
+
console.error('widgetHistory exception', e);
|
|
632
|
+
this.setStatus('online');
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async sendMessage() {
|
|
637
|
+
const input = this._ui.input;
|
|
638
|
+
if (!input) return;
|
|
639
|
+
const text = input.value.trim();
|
|
640
|
+
if (!text) return;
|
|
641
|
+
if (!this.conversation_id) {
|
|
642
|
+
await this.startConversation();
|
|
643
|
+
if (!this.conversation_id) return;
|
|
644
|
+
}
|
|
645
|
+
input.value = '';
|
|
646
|
+
this._ui.send.disabled = true;
|
|
647
|
+
|
|
648
|
+
const userMsg = { role: 'user', content: text, created_at: new Date().toISOString() };
|
|
649
|
+
this.messages.push(userMsg);
|
|
650
|
+
this.renderMessages();
|
|
651
|
+
this.setStatus('thinking…');
|
|
652
|
+
|
|
653
|
+
try {
|
|
654
|
+
const payload = {
|
|
655
|
+
conversation_id: this.conversation_id,
|
|
656
|
+
content: text,
|
|
657
|
+
role: 'user',
|
|
658
|
+
assistant_id: this.assistant_id || undefined,
|
|
659
|
+
model: this.model || undefined
|
|
660
|
+
};
|
|
661
|
+
const resp = await fetch(this.apiBase + '/API/plugin/chatgpt/widgetMessage', {
|
|
662
|
+
method: 'POST',
|
|
663
|
+
headers: { 'Content-Type': 'application/json' },
|
|
664
|
+
body: JSON.stringify(payload)
|
|
665
|
+
}).then(r => r.json());
|
|
666
|
+
|
|
667
|
+
if (!resp || resp.success !== true) {
|
|
668
|
+
const msg = (resp && resp.error) || 'Failed to send message';
|
|
669
|
+
console.error('widgetMessage error', resp);
|
|
670
|
+
this.messages.push({ role:'assistant', content:'[Error: '+msg+']', created_at:new Date().toISOString() });
|
|
671
|
+
this.renderMessages();
|
|
672
|
+
this.setStatus('error: ' + msg);
|
|
673
|
+
} else {
|
|
674
|
+
this.assistant_id = resp.assistant_id || this.assistant_id;
|
|
675
|
+
this.model = resp.model || this.model;
|
|
676
|
+
this.messages = resp.messages || this.messages;
|
|
677
|
+
this.renderMessages();
|
|
678
|
+
this.setStatus('online');
|
|
679
|
+
}
|
|
680
|
+
} catch (e) {
|
|
681
|
+
console.error('widgetMessage exception', e);
|
|
682
|
+
this.setStatus('error');
|
|
683
|
+
} finally {
|
|
684
|
+
this._ui.send.disabled = false;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (!customElements.get('joe-ai-widget')) {
|
|
690
|
+
customElements.define('joe-ai-widget', JoeAIWidget);
|
|
691
|
+
}
|
|
692
|
+
|
|
411
693
|
|
|
412
694
|
//**YES**
|
|
413
695
|
Ai.spawnChatHelper = async function(object_id,user_id=_joe.User._id,conversation_id) {
|
|
@@ -620,6 +902,123 @@
|
|
|
620
902
|
console.error("❌ injectSystemMessage failed:", err);
|
|
621
903
|
}
|
|
622
904
|
};
|
|
905
|
+
|
|
906
|
+
// ---------- Autofill (Completions) ----------
|
|
907
|
+
// Usage in schema button: onclick: "_joe.Ai.populateField(this, { prompt: 'Write a short summary', saveHistory: true })"
|
|
908
|
+
Ai.populateField = async function(dom, options = {}){
|
|
909
|
+
try{
|
|
910
|
+
const obj = _joe.current && _joe.current.object;
|
|
911
|
+
const schema = _joe.current && _joe.current.schema && _joe.current.schema.__schemaname;
|
|
912
|
+
if(!obj || !schema){ return alert('No current object/schema found'); }
|
|
913
|
+
|
|
914
|
+
const parentField = $(dom).parents('.joe-object-field');
|
|
915
|
+
const inferredField = parentField.data('name');
|
|
916
|
+
const fields = (options.fields && options.fields.length) ? options.fields : (inferredField ? [inferredField] : []);
|
|
917
|
+
if(!fields.length){ return alert('No target field detected. Pass options.fields or place the button inside a field.'); }
|
|
918
|
+
|
|
919
|
+
// UI feedback
|
|
920
|
+
const originalHtml = dom.innerHTML;
|
|
921
|
+
dom.disabled = true;
|
|
922
|
+
dom.innerHTML = (options.loadingLabel || 'Thinking...');
|
|
923
|
+
|
|
924
|
+
const payload = {
|
|
925
|
+
object_id: obj._id,
|
|
926
|
+
schema: schema,
|
|
927
|
+
fields: fields,
|
|
928
|
+
prompt: options.prompt || '',
|
|
929
|
+
assistant_id: options.assistant_id || undefined,
|
|
930
|
+
allow_web: !!options.allowWeb,
|
|
931
|
+
save_history: !!options.saveHistory,
|
|
932
|
+
save_itemtype: options.saveItemtype || undefined,
|
|
933
|
+
model: options.model || undefined
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
const resp = await fetch('/API/plugin/chatgpt/autofill', {
|
|
937
|
+
method: 'POST',
|
|
938
|
+
headers: { 'Content-Type': 'application/json' },
|
|
939
|
+
body: JSON.stringify(payload)
|
|
940
|
+
}).then(r => r.json());
|
|
941
|
+
|
|
942
|
+
if(!resp || resp.success !== true){
|
|
943
|
+
console.error('AI autofill error:', resp && resp.error);
|
|
944
|
+
alert('AI autofill failed' + (resp && resp.error ? (': ' + resp.error) : ''));
|
|
945
|
+
}else{
|
|
946
|
+
const patch = resp.patch || {};
|
|
947
|
+
Ai.applyAutofillPatch(patch);
|
|
948
|
+
if(options.autoSave === true){
|
|
949
|
+
_joe.updateObject(null, null, true, null, options.skipValidation === true);
|
|
950
|
+
}
|
|
951
|
+
if(options.openChatAfter === true){
|
|
952
|
+
_joe.Ai.spawnChatHelper(obj._id);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
dom.disabled = false;
|
|
957
|
+
dom.innerHTML = originalHtml;
|
|
958
|
+
}catch(e){
|
|
959
|
+
console.error('populateField error', e);
|
|
960
|
+
alert('Error running AI autofill');
|
|
961
|
+
try{ dom.disabled = false; }catch(_e){}
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
Ai.applyAutofillPatch = function(patch = {}){
|
|
966
|
+
Object.keys(patch).forEach(function(fname){
|
|
967
|
+
Ai._setFieldValue(fname, patch[fname]);
|
|
968
|
+
});
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
Ai._setFieldValue = function(fieldName, value){
|
|
972
|
+
const $container = $(`.joe-object-field[data-name="${fieldName}"]`);
|
|
973
|
+
if(!$container.length){ return false; }
|
|
974
|
+
const $el = $container.find('.joe-field').eq(0);
|
|
975
|
+
if(!$el.length){ return false; }
|
|
976
|
+
|
|
977
|
+
const ftype = $el.data('ftype');
|
|
978
|
+
try{
|
|
979
|
+
switch(ftype){
|
|
980
|
+
case 'tinymce': {
|
|
981
|
+
const editorId = $el.data('texteditor_id');
|
|
982
|
+
const ed = window.tinymce && editorId ? window.tinymce.get(editorId) : null;
|
|
983
|
+
if(ed){ ed.setContent(value || ''); }
|
|
984
|
+
else { $el.val(value || ''); }
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
987
|
+
case 'ckeditor': {
|
|
988
|
+
const editorId = $el.data('ckeditor_id');
|
|
989
|
+
const ed = (window.CKEDITOR && CKEDITOR.instances) ? CKEDITOR.instances[editorId] : null;
|
|
990
|
+
if(ed){ ed.setData(value || ''); }
|
|
991
|
+
else { $el.val(value || ''); }
|
|
992
|
+
break;
|
|
993
|
+
}
|
|
994
|
+
case 'ace': {
|
|
995
|
+
const aceId = $el.data('ace_id');
|
|
996
|
+
const ed = (_joe && _joe.ace_editors) ? _joe.ace_editors[aceId] : null;
|
|
997
|
+
if(ed){ ed.setValue(value || '', -1); }
|
|
998
|
+
else { $el.val(value || ''); }
|
|
999
|
+
break;
|
|
1000
|
+
}
|
|
1001
|
+
default: {
|
|
1002
|
+
if($el.is('select')){
|
|
1003
|
+
$el.val(value);
|
|
1004
|
+
}else if($el.is('input,textarea')){
|
|
1005
|
+
$el.val(value);
|
|
1006
|
+
}else{
|
|
1007
|
+
// Fallback: try innerText/innerHTML for custom components
|
|
1008
|
+
$el.text(typeof value === 'string' ? value : JSON.stringify(value));
|
|
1009
|
+
}
|
|
1010
|
+
$el.trigger('input');
|
|
1011
|
+
$el.trigger('change');
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
$container.addClass('changed');
|
|
1016
|
+
return true;
|
|
1017
|
+
}catch(e){
|
|
1018
|
+
console.warn('Failed to set field', fieldName, e);
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
623
1022
|
|
|
624
1023
|
|
|
625
1024
|
// Attach AI to _joe
|
package/js/joe.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* --------------------------------------------------------
|
|
2
2
|
*
|
|
3
|
-
* json-object-editor - v0.10.
|
|
3
|
+
* json-object-editor - v0.10.610
|
|
4
4
|
* Created by: Corey Hadden
|
|
5
5
|
*
|
|
6
6
|
* -------------------------------------------------------- */
|
|
@@ -4859,8 +4859,9 @@ this.renderHTMLContent = function(specs){
|
|
|
4859
4859
|
$(this).removeClass('editing');
|
|
4860
4860
|
})
|
|
4861
4861
|
if(!stopEditing){
|
|
4862
|
-
$(templateDom).parents('.joe-objectlist-row')
|
|
4863
|
-
$
|
|
4862
|
+
var $row = $(templateDom).parents('.joe-objectlist-row');
|
|
4863
|
+
$row.addClass('editing');
|
|
4864
|
+
$row.find('.joe-text-field,.joe-integer-field,.joe-number-field,select,textarea').eq(0)[0].focus();
|
|
4864
4865
|
}
|
|
4865
4866
|
}
|
|
4866
4867
|
|
|
@@ -8255,12 +8256,28 @@ Field Rendering Helpers
|
|
|
8255
8256
|
//find all tds
|
|
8256
8257
|
var ol_array_obj = {};
|
|
8257
8258
|
$(this).find('td').each(function(i){
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8259
|
+
var $cell = $(this);
|
|
8260
|
+
var $groupCheckboxes = $cell.find('.joe-group-item-checkbox input[type="checkbox"]');
|
|
8261
|
+
var $radios = $cell.find('input[type="radio"]');
|
|
8262
|
+
var $checkboxes = $cell.find('input[type="checkbox"]');
|
|
8263
|
+
|
|
8264
|
+
if ($groupCheckboxes.length) {
|
|
8265
|
+
// Checkbox group inside objectList: collect checked values as an array
|
|
8266
|
+
subVal = $groupCheckboxes.filter(':checked').map(function(){ return $(this).val(); }).get();
|
|
8267
|
+
} else if ($radios.length) {
|
|
8268
|
+
// Single-select group (radio): get selected value or null
|
|
8269
|
+
var r = $radios.filter(':checked').val();
|
|
8270
|
+
subVal = (typeof r === 'undefined') ? null : r;
|
|
8271
|
+
} else if ($checkboxes.length === 1) {
|
|
8272
|
+
// Single boolean checkbox
|
|
8273
|
+
subVal = $checkboxes.is(':checked');
|
|
8274
|
+
} else if ($checkboxes.length > 1) {
|
|
8275
|
+
// Multiple checkboxes without explicit group wrapper: treat as array
|
|
8276
|
+
subVal = $checkboxes.filter(':checked').map(function(){ return $(this).val(); }).get();
|
|
8277
|
+
} else {
|
|
8278
|
+
// Fallback for inputs/selects/textarea
|
|
8279
|
+
subInput = $cell.find('input,select,textarea');
|
|
8280
|
+
subVal = subInput.val();
|
|
8264
8281
|
}
|
|
8265
8282
|
ol_array_obj[subprops[i]] = subVal;
|
|
8266
8283
|
});
|
|
@@ -9685,7 +9702,8 @@ logit(intent)
|
|
|
9685
9702
|
var field = data.field;
|
|
9686
9703
|
var bucket = field.bucketname || (_joe.Data.setting.where({name:'AWS_BUCKETNAME'})[0] || {value:''}).value;
|
|
9687
9704
|
if(!bucket){
|
|
9688
|
-
|
|
9705
|
+
var errMsg = 'AWS bucket not configured';
|
|
9706
|
+
callback && callback(errMsg);
|
|
9689
9707
|
return;
|
|
9690
9708
|
}
|
|
9691
9709
|
var filename = file.filename;
|
|
@@ -9703,72 +9721,47 @@ logit(intent)
|
|
|
9703
9721
|
Key:Key,
|
|
9704
9722
|
base64:file.base64,
|
|
9705
9723
|
extension:file.extension,
|
|
9706
|
-
|
|
9724
|
+
contentType:file.type,
|
|
9725
|
+
ACL:(field.hasOwnProperty('ACL') ? field.ACL : 'public-read')
|
|
9707
9726
|
},
|
|
9708
9727
|
success:function(response){
|
|
9709
9728
|
var url,filename;
|
|
9710
9729
|
if(!response.error){
|
|
9711
9730
|
logit(response);
|
|
9712
|
-
|
|
9713
|
-
var
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9731
|
+
// Prefer server-returned URL; fallback kept for legacy
|
|
9732
|
+
var url = response.url;
|
|
9733
|
+
if(!url){
|
|
9734
|
+
var bucket = response.bucket || (_joe.Data.setting.where({name:'AWS_BUCKETNAME'})[0] || {value:''}).value;
|
|
9735
|
+
var region = (_joe.Utils.Settings('AWS_S3CONFIG') ||{}).region;
|
|
9736
|
+
var prefix = (_joe.Data.setting.where({name:'AWS_S3PREFIX'})[0] || {value:''}).value || 'https://'+bucket+'.s3.'+((region)||'')+'.amazonaws.com/';
|
|
9737
|
+
url = prefix+response.Key;
|
|
9738
|
+
}
|
|
9718
9739
|
var filename = response.Key.replace(self.current.object._id+'/','');
|
|
9719
|
-
//
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
}
|
|
9723
|
-
|
|
9724
|
-
callback(response.error,url,filename);
|
|
9725
|
-
}
|
|
9726
|
-
})
|
|
9727
|
-
},
|
|
9728
|
-
awsConnect:function(data,callback){
|
|
9729
|
-
var bucket = data.field.bucketname || (_joe.Data.setting.where({name:'AWS_BUCKETNAME'})[0] || {value:''}).value;
|
|
9730
|
-
if(!bucket){
|
|
9731
|
-
callback(err,data.base64);
|
|
9732
|
-
return;
|
|
9733
|
-
}
|
|
9734
|
-
var filename = _joe.current.object._id+data.extension;
|
|
9735
|
-
var directory = _joe.current.object.itemtype;
|
|
9736
|
-
var url = (field && self.propAsFuncOrValue(field.server_url)) ||
|
|
9737
|
-
(location.port && '//'+__jsc.hostname+':'+__jsc.port+'/API/plugin/awsConnect/') ||
|
|
9738
|
-
'//'+__jsc.hostname+'/API/plugin/awsConnect/';
|
|
9739
|
-
var Key = directory+'/'+filename;
|
|
9740
|
-
var field = data.field;
|
|
9741
|
-
$.ajax({
|
|
9742
|
-
url:url,
|
|
9743
|
-
type:'POST',
|
|
9744
|
-
data:{
|
|
9745
|
-
Key:Key,
|
|
9746
|
-
base64:data.base64,
|
|
9747
|
-
extension:data.extension,
|
|
9748
|
-
ACL:'public-read'
|
|
9749
|
-
},
|
|
9750
|
-
success:function(data){
|
|
9751
|
-
if(!data.error){
|
|
9752
|
-
logit(data);
|
|
9753
|
-
var bucket = data.bucket || (_joe.Data.setting.where({name:'AWS_BUCKETNAME'})[0] || {value:''}).value;
|
|
9754
|
-
|
|
9755
|
-
//var prefix = 'https://s3.amazonaws.com/'+bucket+'/';
|
|
9756
|
-
var prefix = 'https://'+bucket+'.s3.amazonaws.com/';
|
|
9757
|
-
|
|
9758
|
-
var url = prefix+data.Key;
|
|
9740
|
+
// If a url_field is specified, set it and rerender
|
|
9759
9741
|
if(field.url_field){
|
|
9760
9742
|
var nprop = {};
|
|
9761
9743
|
_joe.current.object[field.url_field] = url;
|
|
9762
9744
|
nprop[field.url_field] = url;
|
|
9763
|
-
/*$('.joe-field[name="'+field.url_field+'"]').val(url)*/
|
|
9764
9745
|
_joe.Fields.rerender(field.url_field,nprop);
|
|
9765
9746
|
}
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
callback(data.error);
|
|
9747
|
+
|
|
9748
|
+
|
|
9769
9749
|
}
|
|
9750
|
+
|
|
9751
|
+
callback(response.error,url,filename);
|
|
9752
|
+
},
|
|
9753
|
+
error:function(xhr,status,err){
|
|
9754
|
+
var message = (xhr && (xhr.responseJSON && (xhr.responseJSON.error || xhr.responseJSON.code) || xhr.responseText)) || err || status || 'Upload failed';
|
|
9755
|
+
callback && callback(message);
|
|
9770
9756
|
}
|
|
9771
9757
|
})
|
|
9758
|
+
},
|
|
9759
|
+
awsConnect:function(data,callback){
|
|
9760
|
+
// Backward-compat wrapper: delegate to awsFileUpload
|
|
9761
|
+
return _joe.SERVER.Plugins.awsFileUpload({
|
|
9762
|
+
file: { base64:data.base64, extension:data.extension, type:data.contentType, filename: (_joe.current.object._id+data.extension) },
|
|
9763
|
+
field: data.field
|
|
9764
|
+
}, callback);
|
|
9772
9765
|
}
|
|
9773
9766
|
},
|
|
9774
9767
|
ready:{
|