aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/static
diff options
context:
space:
mode:
Diffstat (limited to 'internal/admin/static')
-rw-r--r--internal/admin/static/admin.css3
-rw-r--r--internal/admin/static/intereditor.js523
-rw-r--r--internal/admin/static/talktree.js731
3 files changed, 504 insertions, 753 deletions
diff --git a/internal/admin/static/admin.css b/internal/admin/static/admin.css
index 953c392..efe5abc 100644
--- a/internal/admin/static/admin.css
+++ b/internal/admin/static/admin.css
@@ -389,7 +389,7 @@ g.ud-hover:hover text{font-weight:bold}
.ie-cond-addbar{display:grid;grid-template-columns:repeat(auto-fit,minmax(max(100%/5,110px),1fr));gap:4px;margin-top:4px}
.ie-add-btn{font-size:10px!important;padding:2px 6px!important;background:var(--accent)!important;color:var(--text)!important;border:1px solid #1a5a8e!important;font-family:monospace!important;border-radius:3px!important;cursor:pointer;white-space:nowrap}
.ie-add-btn:hover{background:#1a5a8e!important;color:#fff!important}
-.ie-cond-addbar .ie-add-btn,.ie-step-addbar .ie-add-btn,.ie-step-addbar-bottom .ie-add-btn{width:100%;min-width:0}
+.ie-cond-addbar .ie-add-btn,.ie-step-addbar .ie-add-btn,.ie-step-addbar-bottom .ie-add-btn,.ie-act-addbar .ie-add-btn{width:100%;min-width:0}
/* Action editor */
.ie-act-root{background:rgba(100,160,255,.04);border:1px solid rgba(100,160,255,.12);border-radius:5px;padding:8px;display:flex;flex-direction:column;gap:6px}
@@ -441,6 +441,7 @@ g.ud-hover:hover text{font-weight:bold}
.ie-step-addbar{display:none;flex-wrap:wrap;gap:4px;margin-top:2px}
.ie-step-row.show-addbar .ie-step-addbar{display:grid;grid-template-columns:repeat(auto-fit,minmax(max(100%/5,110px),1fr))}
.ie-step-addbar-bottom{display:grid;grid-template-columns:repeat(auto-fit,minmax(max(100%/5,110px),1fr));gap:4px;margin-top:6px}
+.ie-act-addbar{display:grid;grid-template-columns:repeat(auto-fit,minmax(max(100%/5,110px),1fr));gap:4px;margin-top:2px}
/* ── Message rows within a step ── */
.ie-msg-row{display:flex;align-items:center;gap:4px}
diff --git a/internal/admin/static/intereditor.js b/internal/admin/static/intereditor.js
index 0e2c03d..152aef3 100644
--- a/internal/admin/static/intereditor.js
+++ b/internal/admin/static/intereditor.js
@@ -51,6 +51,132 @@ IE_ACT_TYPES.forEach(function(at) {
IE_STEP_KINDS.push(at);
});
+// ── Scope / Context system ──
+// Lets condition & action editors target data models outside the
+// default ed[secPath][idx].steps[si] arrangement (e.g. talk node/option
+// conditions and single-step actions). When a scope is registered and its
+// token is placed on a DOM root via data-ie-scope, all mutation onclicks
+// resolve their data target through the scope instead of the legacy trigger
+// path.
+
+window._ieScopes = {};
+var _ieScopeSeq = 0;
+
+function ie_registerScope(opts, token) {
+ if (!token) {
+ token = 'sc' + (++_ieScopeSeq);
+ }
+ window._ieScopes[token] = opts;
+ return token;
+}
+window.ie_registerScope = ie_registerScope;
+
+function ie_unregisterScope(token) {
+ delete window._ieScopes[token];
+}
+window.ie_unregisterScope = ie_unregisterScope;
+
+function _ie_scopeFromEl(el) {
+ if (!el || !el.closest) return null;
+ var scopeEl = el.closest('[data-ie-scope]');
+ if (!scopeEl) return null;
+ var token = scopeEl.getAttribute('data-ie-scope');
+ return window._ieScopes[token] || null;
+}
+
+// Resolve the .ie-cond-root DOM element scoped to a mutation element el.
+// Handles both the root itself and the outer addbar (which sits outside
+// .ie-cond-root as a sibling).
+function _ie_condScopeRoot(el) {
+ var root = el.closest('.ie-cond-root');
+ if (root) return root;
+ var addbar = el.closest('.ie-cond-addbar');
+ if (!addbar) return null;
+ var parent = addbar.parentElement;
+ if (!parent) return null;
+ return parent.querySelector('.ie-cond-root');
+}
+
+// Unified condition-editing context.
+// Returns { get, set, change } — `get` always syncs DOM → data store
+// before returning. Routes through a registered scope when el lives inside
+// [data-ie-scope], otherwise through the legacy ed[secPath][idx] trigger path.
+function _ie_condCtx(el, secId, secPath, idx, kind) {
+ var scope = _ie_scopeFromEl(el);
+ if (scope) {
+ return {
+ get: function() {
+ var root = _ie_condScopeRoot(el);
+ if (root) {
+ var cond = ie_collectCond(root);
+ scope.setCond(cond);
+ }
+ return scope.getCond();
+ },
+ set: function(c) { scope.setCond(c); },
+ change: function() { scope.onChange(); }
+ };
+ }
+ return {
+ get: function() {
+ var ed = window._editorData;
+ if (!ed) return null;
+ _ie_syncCond(ed, secId, secPath, idx, kind);
+ return _ie_getCond(ed, secPath, idx, kind);
+ },
+ set: function(c) {
+ var ed = window._editorData;
+ if (!ed) return;
+ _ie_setCond(ed, secPath, idx, kind, c);
+ ced_syncDOMToData(ed);
+ },
+ change: function() { window._ieRenderOnly(); }
+ };
+}
+
+// Unified action/step-editing context.
+// Returns { get, set, change }. In scope mode reads the .ie-act-root
+// container; in legacy mode operates on ed[secPath][idx].steps[si].
+function _ie_actCtx(el, secId, secPath, idx, si) {
+ var scope = _ie_scopeFromEl(el);
+ if (scope) {
+ return {
+ get: function() {
+ var container = el.closest('.ie-act-root');
+ if (!container) return scope.getStep() || {};
+ var act = ie_collectAct(container);
+ var msgs = ie_collectMessages(container);
+ var step = scope.getStep() || {};
+ if (act) Object.keys(act).forEach(function(k) { step[k] = act[k]; });
+ if (msgs !== null) step.messages = msgs; else delete step.messages;
+ scope.setStep(step);
+ return scope.getStep();
+ },
+ set: function(s) { scope.setStep(s); },
+ change: function() { scope.onChange(); }
+ };
+ }
+ return {
+ get: function() {
+ var ed = window._editorData;
+ if (!ed) return {};
+ ie_syncStep(ed, secId, secPath, idx, si);
+ if (!ed[secPath] || !ed[secPath][idx] || !ed[secPath][idx].steps || !ed[secPath][idx].steps[si]) return {};
+ return ed[secPath][idx].steps[si];
+ },
+ set: function(s) {
+ var ed = window._editorData;
+ if (!ed) return;
+ if (!ed[secPath]) ed[secPath] = [];
+ if (!ed[secPath][idx]) ed[secPath][idx] = {steps: []};
+ if (!ed[secPath][idx].steps) ed[secPath][idx].steps = [];
+ ed[secPath][idx].steps[si] = s;
+ ced_syncDOMToData(ed);
+ },
+ change: function() { window._ieRenderOnly(); }
+ };
+}
+
// ── Rendering helpers ──
// ie_renderStepAddbar: the per-step action add bar revealed by the "+" toggle.
@@ -67,9 +193,9 @@ function ie_renderStepAddbar(step, secId, idx, si, secPath) {
}
}
if (sk.key === 'messages') {
- h += '<button class="btn btn-sm ie-add-btn" onclick="ie_addMessages(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ')">+ ' + esc(sk.label) + '</button>';
+ h += '<button class="btn btn-sm ie-add-btn" onclick="ie_addMessages(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ')">+ ' + esc(sk.label) + '</button>';
} else {
- h += '<button class="btn btn-sm ie-add-btn" onclick="ie_addActEffect(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',\'' + escAttr(sk.key) + '\')">+ ' + esc(sk.label) + '</button>';
+ h += '<button class="btn btn-sm ie-add-btn" onclick="ie_addActEffect(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',\'' + escAttr(sk.key) + '\')">+ ' + esc(sk.label) + '</button>';
}
});
return h;
@@ -80,7 +206,7 @@ function ie_renderStepAddbar(step, secId, idx, si, secPath) {
function ie_renderBottomAddbar(secId, secPath, idx) {
var h = '';
IE_STEP_KINDS.forEach(function(sk) {
- h += '<button class="btn btn-sm ie-add-btn" onclick="ie_addStepKind(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(sk.key) + '\')">+ ' + esc(sk.label) + '</button>';
+ h += '<button class="btn btn-sm ie-add-btn" onclick="ie_addStepKind(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(sk.key) + '\')">+ ' + esc(sk.label) + '</button>';
});
return h;
}
@@ -142,7 +268,7 @@ function ie_collectDropTableBlock(container) {
}
// ie_addStepKind creates a new step initialised with a single action kind.
-function ie_addStepKind(secId, secPath, idx, kind) {
+function ie_addStepKind(el, secId, secPath, idx, kind) {
var ed = window._editorData;
if (!ed) return;
ced_syncDOMToData(ed);
@@ -387,6 +513,8 @@ function ie_collectLeaf(el) {
function ie_renderCond(condObj, secId, idx, secPath, kind, opts) {
kind = kind || 'entry';
+ opts = opts || {};
+ var scopeAttr = opts.scopeToken ? ' data-ie-scope="' + escAttr(opts.scopeToken) + '"' : '';
var h = '';
var isGroup = condObj && (condObj.all_of || condObj.any_of);
var rootChildCount = isGroup ? (condObj.all_of || condObj.any_of || []).length : 0;
@@ -395,21 +523,21 @@ function ie_renderCond(condObj, secId, idx, secPath, kind, opts) {
if (kind && kind.indexOf('step-') === 0) {
var sn = parseInt(kind.slice(5), 10);
if (!isNaN(sn)) condTitleText = 'Condition for Step ' + (sn + 1);
- } else if (kind === 'entry') {
+ } else if (kind === 'entry' && !opts.scopeToken) {
condTitleText = 'Condition for Action Block';
}
- h += '<div class="ie-cond-root">';
+ h += '<div class="ie-cond-root"' + scopeAttr + '>';
if (condTitleText) h += '<div class="ie-cond-title">' + esc(condTitleText) + '</div>';
h += ie_renderGroup(condObj, secId, secPath, idx, '', kind);
h += '</div>';
}
- var showAddbar = !isGroup || (opts && opts.alwaysOuterAddbar && rootChildCount === 0);
+ var showAddbar = (!isGroup || (opts.alwaysOuterAddbar && rootChildCount === 0)) && typeof condObj === 'object';
if (showAddbar) {
- h += '<div class="ie-cond-addbar">';
+ h += '<div class="ie-cond-addbar"' + scopeAttr + '>';
IE_COND_TYPES.forEach(function(ct) {
- h += '<button class="btn btn-sm ie-add-btn ie-cond-add-leaf" onclick="ie_addOuterLeaf(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + ct.type + '\')">+ ' + esc(ct.label) + '</button>';
+ h += '<button class="btn btn-sm ie-add-btn ie-cond-add-leaf" onclick="ie_addOuterLeaf(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + ct.type + '\')">+ ' + esc(ct.label) + '</button>';
});
- h += '<button class="btn btn-sm ie-add-btn ie-cond-add-grp" onclick="ie_addOuterGroup(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\')">+ Group</button>';
+ h += '<button class="btn btn-sm ie-add-btn ie-cond-add-grp" onclick="ie_addOuterGroup(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\')">+ Group</button>';
h += '</div>';
}
return h;
@@ -442,10 +570,10 @@ function _ie_renderGroupNode(mode, children, not, secId, secPath, idx, gpath, ki
h += '<div class="ie-cond ie-cond-empty" data-ie-gpath="' + escAttr(gpath) + '">';
h += '<div class="ie-cond-modebar ie-cond-empty-bar">';
IE_COND_TYPES.forEach(function(ct) {
- h += '<button class="btn btn-sm ie-add-btn ie-cond-add-leaf" onclick="ie_addLeaf(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\',\'' + ct.type + '\')">+ ' + esc(ct.label) + '</button>';
+ h += '<button class="btn btn-sm ie-add-btn ie-cond-add-leaf" onclick="ie_addLeaf(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\',\'' + ct.type + '\')">+ ' + esc(ct.label) + '</button>';
});
- h += '<button class="btn btn-sm ie-add-btn ie-cond-add-grp" onclick="ie_addGroup(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\')">+ Group</button>';
- h += '<button class="btn btn-sm btn-danger ie-cond-grp-rm" onclick="ie_removeGroup(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\')" title="Remove this group">X</button>';
+ h += '<button class="btn btn-sm ie-add-btn ie-cond-add-grp" onclick="ie_addGroup(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\')">+ Group</button>';
+ h += '<button class="btn btn-sm btn-danger ie-cond-grp-rm" onclick="ie_removeGroup(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\')" title="Remove this group">X</button>';
h += '</div>';
h += '</div>';
return h;
@@ -454,13 +582,13 @@ function _ie_renderGroupNode(mode, children, not, secId, secPath, idx, gpath, ki
h += '<div class="ie-cond" data-ie-gpath="' + escAttr(gpath) + '">';
h += '<div class="ie-cond-modebar">';
if (children.length >= 2) {
- h += '<select class="ie-cond-modesel" onchange="ie_toggleCondMode(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\')">';
+ h += '<select class="ie-cond-modesel" onchange="ie_toggleCondMode(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\')">';
h += '<option value="all_of"' + (mode === 'all_of' ? ' selected' : '') + '>All of</option>';
h += '<option value="any_of"' + (mode === 'any_of' ? ' selected' : '') + '>Any of</option>';
h += '</select>';
}
if (children.length >= 2 || not) {
- h += '<label class="ie-cond-notlbl"><input type="checkbox" class="ie-cond-not" onchange="ie_toggleCondNot(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\')"' + (not ? ' checked' : '') + '> NOT</label>';
+ h += '<label class="ie-cond-notlbl"><input type="checkbox" class="ie-cond-not" onchange="ie_toggleCondNot(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\')"' + (not ? ' checked' : '') + '> NOT</label>';
}
h += '</div>';
h += '<div class="ie-cond-children">';
@@ -481,9 +609,9 @@ function _ie_renderGroupNode(mode, children, not, secId, secPath, idx, gpath, ki
h += '</div>';
h += '<div class="ie-cond-addbar" data-ie-gpath="' + escAttr(gpath) + '">';
IE_COND_TYPES.forEach(function(ct) {
- h += '<button class="btn btn-sm ie-add-btn ie-cond-add-leaf" onclick="ie_addLeaf(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\',\'' + ct.type + '\')">+ ' + esc(ct.label) + '</button>';
+ h += '<button class="btn btn-sm ie-add-btn ie-cond-add-leaf" onclick="ie_addLeaf(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\',\'' + ct.type + '\')">+ ' + esc(ct.label) + '</button>';
});
- h += '<button class="btn btn-sm ie-add-btn ie-cond-add-grp" onclick="ie_addGroup(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\')">+ Group</button>';
+ h += '<button class="btn btn-sm ie-add-btn ie-cond-add-grp" onclick="ie_addGroup(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\')">+ Group</button>';
h += '</div>';
h += '</div>';
return h;
@@ -516,8 +644,8 @@ function _ie_renderLeaf(leafType, leafVal, leafSub, not, secId, secPath, idx, gp
h += '<span class="ie-cond-eq">=</span>';
h += '<input class="ie-cond-subval" placeholder="exists" value="' + escAttr(ssv) + '">';
}
- h += '<label class="ie-cond-notlbl"><input type="checkbox" class="ie-cond-not" onchange="ie_toggleNotLeaf(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\',' + ciToken + ')"' + (not ? ' checked' : '') + '> NOT</label>';
- h += '<button class="btn btn-sm btn-danger ie-cond-rm" onclick="ie_removeCond(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\',' + ciToken + ')" title="Remove">X</button>';
+ h += '<label class="ie-cond-notlbl"><input type="checkbox" class="ie-cond-not" onchange="ie_toggleNotLeaf(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\',' + ciToken + ')"' + (not ? ' checked' : '') + '> NOT</label>';
+ h += '<button class="btn btn-sm btn-danger ie-cond-rm" onclick="ie_removeCond(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(kind) + '\',\'' + escAttr(gpath) + '\',' + ciToken + ')" title="Remove">X</button>';
h += '</div>';
return h;
}
@@ -554,27 +682,27 @@ function ie_renderAct(stepObj, secId, idx, si, secPath) {
});
}
rows += '</div>';
- rows += '<button class="btn btn-sm ie-add-btn" style="margin-top:2px" onclick="ie_addKVRow(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',\'' + escAttr(at.key) + '\')">+ Add</button>';
+ rows += '<button class="btn btn-sm ie-add-btn" style="margin-top:2px" onclick="ie_addKVRow(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',\'' + escAttr(at.key) + '\')">+ Add</button>';
rows += '</div>';
} else if (at.kind === 'checkbox') {
rows += '<div class="ie-act-row"><span class="ie-act-label">' + esc(at.label) + '</span>';
rows += '<input type="checkbox" class="ie-act-input" data-ie-act-key="' + escAttr(at.key) + '" data-ie-act-kind="checkbox" checked hidden>';
- rows += '<button class="btn btn-sm btn-danger ie-act-rm" onclick="ie_removeActEffect(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',\'' + escAttr(at.key) + '\')">X</button>';
+ rows += '<button class="btn btn-sm btn-danger ie-act-rm" onclick="ie_removeActEffect(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',\'' + escAttr(at.key) + '\')">X</button>';
rows += '</div>';
} else if (at.kind === 'search') {
rows += '<div class="ie-act-row"><span class="ie-act-label">' + esc(at.label) + '</span>';
rows += '<div class="obj-search" style="position:relative;min-width:0;flex:1"><input class="ie-act-input search-input" data-ie-act-key="' + escAttr(at.key) + '" data-search-type="' + escAttr(at.entity) + '" placeholder="' + escAttr(at.hint || 'Search ' + at.entity + '...') + '" value="' + escAttr(String(val)) + '"><div class="search-results" style="display:none"></div></div>';
- rows += '<button class="btn btn-sm btn-danger ie-act-rm" onclick="ie_removeActEffect(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',\'' + escAttr(at.key) + '\')">X</button>';
+ rows += '<button class="btn btn-sm btn-danger ie-act-rm" onclick="ie_removeActEffect(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',\'' + escAttr(at.key) + '\')">X</button>';
rows += '</div>';
} else if (at.kind === 'droptable') {
rows += '<div class="ie-act-kv" data-ie-act-key="drop_table">';
- rows += '<div class="ie-act-kv-header"><span>Drop Table</span><button class="btn btn-sm btn-danger ie-act-rm" style="margin-left:auto" onclick="ie_removeActEffect(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',\'drop_table\')">X</button></div>';
- rows += ie_renderDropTableBlock(val, 'ie_addDropRow(\''+escAttr(secId)+'\',\''+escAttr(secPath)+'\','+idx+','+si+',\'item\')', 'ie_addDropRow(\''+escAttr(secId)+'\',\''+escAttr(secPath)+'\','+idx+','+si+',\'table\')', 'ie_removeDropRow(\''+escAttr(secId)+'\',\''+escAttr(secPath)+'\','+idx+','+si+',__RI__)');
+ rows += '<div class="ie-act-kv-header"><span>Drop Table</span><button class="btn btn-sm btn-danger ie-act-rm" style="margin-left:auto" onclick="ie_removeActEffect(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',\'drop_table\')">X</button></div>';
+ rows += ie_renderDropTableBlock(val, 'ie_addDropRow(this,\''+escAttr(secId)+'\',\''+escAttr(secPath)+'\','+idx+','+si+',\'item\')', 'ie_addDropRow(this,\''+escAttr(secId)+'\',\''+escAttr(secPath)+'\','+idx+','+si+',\'table\')', 'ie_removeDropRow(this,\''+escAttr(secId)+'\',\''+escAttr(secPath)+'\','+idx+','+si+',__RI__)');
rows += '</div>';
} else {
rows += '<div class="ie-act-row"><span class="ie-act-label">' + esc(at.label) + '</span>';
rows += '<input class="ie-act-input" data-ie-act-key="' + escAttr(at.key) + '"' + (at.kind === 'number' ? ' type="number"' : '') + ' data-ie-act-kind="' + escAttr(at.kind) + '" value="' + escAttr(String(val)) + '" placeholder="' + escAttr(at.hint || '') + '" style="flex:1">';
- rows += '<button class="btn btn-sm btn-danger ie-act-rm" onclick="ie_removeActEffect(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',\'' + escAttr(at.key) + '\')">X</button>';
+ rows += '<button class="btn btn-sm btn-danger ie-act-rm" onclick="ie_removeActEffect(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',\'' + escAttr(at.key) + '\')">X</button>';
rows += '</div>';
}
});
@@ -599,15 +727,15 @@ function ie_renderMessages(step, secId, idx, si, secPath) {
h += '<div class="ie-kv-list">';
msgs.forEach(function(m, mi) {
var upBtn = mi > 0
- ? '<button class="btn btn-sm ie-msg-up" onclick="ie_moveMessage(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',' + mi + ',' + (mi - 1) + ')" title="Move up">&#9650;</button>'
+ ? '<button class="btn btn-sm ie-msg-up" onclick="ie_moveMessage(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',' + mi + ',' + (mi - 1) + ')" title="Move up">&#9650;</button>'
: '';
var dnBtn = mi < msgs.length - 1
- ? '<button class="btn btn-sm ie-msg-dn" onclick="ie_moveMessage(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',' + mi + ',' + (mi + 1) + ')" title="Move down">&#9660;</button>'
+ ? '<button class="btn btn-sm ie-msg-dn" onclick="ie_moveMessage(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',' + mi + ',' + (mi + 1) + ')" title="Move down">&#9660;</button>'
: '';
h += '<div class="ie-kv-row ie-msg-row"><input class="ie-act-msg" data-ie-msg-idx="' + mi + '" value="' + escAttr(String(m)) + '" placeholder="Shown to player" style="flex:1">' + upBtn + dnBtn + '<button class="btn btn-sm btn-danger" onclick="' + rmFn + '">X</button></div>';
});
h += '</div>';
- h += '<button class="btn btn-sm ie-add-btn" style="margin-top:2px" onclick="ie_addMessage(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ')">+ Add Message</button>';
+ h += '<button class="btn btn-sm ie-add-btn" style="margin-top:2px" onclick="ie_addMessage(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ')">+ Add Message</button>';
h += '</div>';
return h;
}
@@ -624,12 +752,12 @@ function ie_renderStepList(steps, secId, idx, secPath) {
h += '<span class="ie-step-label">Step ' + (si + 1) + '</span>';
h += '<button class="btn btn-sm ie-step-addmore" onclick="this.closest(\'.ie-step-row\').classList.toggle(\'show-addbar\')" title="Add action to this step"></button>';
if (!stepCond) {
- h += '<button class="btn btn-sm ie-add-btn" onclick="ie_addStepCond(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ')">+ Add Step Condition</button>';
+ h += '<button class="btn btn-sm ie-add-btn" onclick="ie_addStepCond(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ')">+ Add Step Condition</button>';
}
h += '<span class="ie-step-spacer"></span>';
- if (si > 0) h += '<button class="btn btn-sm ie-step-up" onclick="ie_moveStep(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',' + (si - 1) + ')" title="Move up">&#9650;</button>';
- if (si < steps.length - 1) h += '<button class="btn btn-sm ie-step-dn" onclick="ie_moveStep(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',' + (si + 1) + ')" title="Move down">&#9660;</button>';
- h += '<button class="btn btn-sm btn-danger" onclick="ie_removeStep(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ')" title="Remove step">X</button>';
+ if (si > 0) h += '<button class="btn btn-sm ie-step-up" onclick="ie_moveStep(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',' + (si - 1) + ')" title="Move up">&#9650;</button>';
+ if (si < steps.length - 1) h += '<button class="btn btn-sm ie-step-dn" onclick="ie_moveStep(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',' + (si + 1) + ')" title="Move down">&#9660;</button>';
+ h += '<button class="btn btn-sm btn-danger" onclick="ie_removeStep(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ')" title="Remove step">X</button>';
h += '</div>';
if (stepCond) {
@@ -789,16 +917,12 @@ function ie_syncAllInteractions(data, secs) {
// ── Add/Remove/Toggle: Condition ──
-function ie_addLeaf(secId, secPath, idx, kind, groupPath, leafType) {
- var ed = window._editorData;
- if (!ed) return;
- _ie_syncCond(ed, secId, secPath, idx, kind);
- var cond = _ie_getCond(ed, secPath, idx, kind);
- var group = ie_condAt(cond, groupPath);
-
+function ie_addLeaf(el, secId, secPath, idx, kind, groupPath, leafType) {
+ var c = _ie_condCtx(el, secId, secPath, idx, kind);
+ var cond = c.get();
var leaf = {};
leaf[leafType] = (leafType === 'min_credits' || leafType === 'room') ? 0 : '';
-
+ var group = ie_condAt(cond, groupPath);
var newGroup;
if (!group) {
newGroup = leaf;
@@ -811,29 +935,21 @@ function ie_addLeaf(secId, secPath, idx, kind, groupPath, leafType) {
} else {
newGroup = {all_of: [group, leaf]};
}
-
- var newCond = ie_condReplace(cond, groupPath, newGroup);
- _ie_setCond(ed, secPath, idx, kind, newCond);
- ced_syncDOMToData(ed);
- window._ieRenderOnly();
+ cond = ie_condReplace(cond, groupPath, newGroup);
+ c.set(cond);
+ c.change();
}
-function ie_removeCond(secId, secPath, idx, kind, groupPath, childIdx) {
- var ed = window._editorData;
- if (!ed) return;
- _ie_syncCond(ed, secId, secPath, idx, kind);
- var cond = _ie_getCond(ed, secPath, idx, kind);
-
+function ie_removeCond(el, secId, secPath, idx, kind, groupPath, childIdx) {
+ var c = _ie_condCtx(el, secId, secPath, idx, kind);
+ var cond = c.get();
if (childIdx === IE_ROOT_LEAF) {
- _ie_setCond(ed, secPath, idx, kind, null);
- ced_syncDOMToData(ed);
- window._ieRenderOnly();
+ c.set(null);
+ c.change();
return;
}
-
var group = ie_condAt(cond, groupPath);
if (!group) return;
-
if (group.all_of || group.any_of) {
var mode = group.all_of ? 'all_of' : 'any_of';
var children = (group.all_of || group.any_of).slice();
@@ -848,39 +964,30 @@ function ie_removeCond(secId, secPath, idx, kind, groupPath, childIdx) {
if (group.not) newGroup.not = true;
newGroup[mode] = children;
}
- var newCond = ie_condReplace(cond, groupPath, newGroup);
- _ie_setCond(ed, secPath, idx, kind, newCond);
+ cond = ie_condReplace(cond, groupPath, newGroup);
} else {
- var nc = ie_condReplace(cond, groupPath, null);
- _ie_setCond(ed, secPath, idx, kind, nc);
+ cond = ie_condReplace(cond, groupPath, null);
}
- ced_syncDOMToData(ed);
- window._ieRenderOnly();
+ c.set(cond);
+ c.change();
}
-function ie_removeGroup(secId, secPath, idx, kind, groupPath) {
- var ed = window._editorData;
- if (!ed) return;
- _ie_syncCond(ed, secId, secPath, idx, kind);
- var cond = _ie_getCond(ed, secPath, idx, kind);
-
+function ie_removeGroup(el, secId, secPath, idx, kind, groupPath) {
+ var c = _ie_condCtx(el, secId, secPath, idx, kind);
+ var cond = c.get();
if (!groupPath) {
- _ie_setCond(ed, secPath, idx, kind, null);
- ced_syncDOMToData(ed);
- window._ieRenderOnly();
+ c.set(null);
+ c.change();
return;
}
-
var parts = groupPath.split('-').map(Number);
var childIdx = parts[parts.length - 1];
var parentPath = parts.slice(0, -1).join('-');
var parent = ie_condAt(cond, parentPath);
if (!parent || !(parent.all_of || parent.any_of)) return;
-
var mode = parent.all_of ? 'all_of' : 'any_of';
var children = parent[mode].slice();
children.splice(childIdx, 1);
-
var newParent;
if (children.length === 0) {
newParent = null;
@@ -891,11 +998,9 @@ function ie_removeGroup(secId, secPath, idx, kind, groupPath) {
if (parent.not) newParent.not = true;
newParent[mode] = children;
}
-
- var newCond = ie_condReplace(cond, parentPath, newParent);
- _ie_setCond(ed, secPath, idx, kind, newCond);
- ced_syncDOMToData(ed);
- window._ieRenderOnly();
+ cond = ie_condReplace(cond, parentPath, newParent);
+ c.set(cond);
+ c.change();
}
function ie_addRootConditionGroup(secPath, idx, kind) {
@@ -919,19 +1024,21 @@ function ie_addRootConditionGroup(secPath, idx, kind) {
window._ieRenderOnly();
}
-function ie_addStepCond(secId, secPath, idx, si) {
+function ie_addStepCond(el, secId, secPath, idx, si) {
+ if (el && el.nodeType && _ie_scopeFromEl(el)) {
+ var c = _ie_condCtx(el, secId, secPath, idx, 'step-' + si);
+ c.get();
+ c.change();
+ return;
+ }
ie_addRootConditionGroup(secPath, idx, 'step-' + si);
}
-function ie_addGroup(secId, secPath, idx, kind, groupPath) {
- var ed = window._editorData;
- if (!ed) return;
- _ie_syncCond(ed, secId, secPath, idx, kind);
- var cond = _ie_getCond(ed, secPath, idx, kind);
+function ie_addGroup(el, secId, secPath, idx, kind, groupPath) {
+ var c = _ie_condCtx(el, secId, secPath, idx, kind);
+ var cond = c.get();
var group = ie_condAt(cond, groupPath);
-
var newSub = {all_of: []};
-
var newGroup;
if (!group) {
newGroup = newSub;
@@ -944,78 +1051,59 @@ function ie_addGroup(secId, secPath, idx, kind, groupPath) {
} else {
newGroup = {all_of: [group, newSub]};
}
-
- var newCond = ie_condReplace(cond, groupPath, newGroup);
- _ie_setCond(ed, secPath, idx, kind, newCond);
- ced_syncDOMToData(ed);
- window._ieRenderOnly();
+ cond = ie_condReplace(cond, groupPath, newGroup);
+ c.set(cond);
+ c.change();
}
-function ie_addOuterLeaf(secId, secPath, idx, kind, leafType) {
- var ed = window._editorData;
- if (!ed) return;
- _ie_syncCond(ed, secId, secPath, idx, kind);
- var cond = _ie_getCond(ed, secPath, idx, kind);
-
+function ie_addOuterLeaf(el, secId, secPath, idx, kind, leafType) {
+ var c = _ie_condCtx(el, secId, secPath, idx, kind);
+ var cond = c.get();
var leaf = {};
leaf[leafType] = (leafType === 'min_credits' || leafType === 'room') ? 0 : '';
-
- var newCond;
if (!cond) {
- newCond = leaf;
+ cond = leaf;
} else {
- newCond = {all_of: [cond, leaf]};
+ cond = {all_of: [cond, leaf]};
}
-
- _ie_setCond(ed, secPath, idx, kind, newCond);
- ced_syncDOMToData(ed);
- window._ieRenderOnly();
+ c.set(cond);
+ c.change();
}
-function ie_addOuterGroup(secId, secPath, idx, kind) {
- var ed = window._editorData;
- if (!ed) return;
- _ie_syncCond(ed, secId, secPath, idx, kind);
- var cond = _ie_getCond(ed, secPath, idx, kind);
-
+function ie_addOuterGroup(el, secId, secPath, idx, kind) {
+ var c = _ie_condCtx(el, secId, secPath, idx, kind);
+ var cond = c.get();
var newSub = {all_of: []};
-
- var newCond;
if (!cond) {
- newCond = newSub;
+ cond = newSub;
} else {
- newCond = {all_of: [cond, newSub]};
+ cond = {all_of: [cond, newSub]};
}
-
- _ie_setCond(ed, secPath, idx, kind, newCond);
- ced_syncDOMToData(ed);
- window._ieRenderOnly();
+ c.set(cond);
+ c.change();
}
-function ie_toggleCondMode(secId, secPath, idx, kind, groupPath) {
- var ed = window._editorData;
- if (!ed) return;
- _ie_syncCond(ed, secId, secPath, idx, kind);
- window._ieRenderOnly();
+function ie_toggleCondMode(el, secId, secPath, idx, kind, groupPath) {
+ var c = _ie_condCtx(el, secId, secPath, idx, kind);
+ c.get();
+ c.change();
}
-function ie_toggleCondNot(secId, secPath, idx, kind, groupPath) {
- var ed = window._editorData;
- if (!ed) return;
- _ie_syncCond(ed, secId, secPath, idx, kind);
- window._ieRenderOnly();
+function ie_toggleCondNot(el, secId, secPath, idx, kind, groupPath) {
+ var c = _ie_condCtx(el, secId, secPath, idx, kind);
+ c.get();
+ c.change();
}
-function ie_toggleNotLeaf(secId, secPath, idx, kind, groupPath, ci) {
- var ed = window._editorData;
- if (!ed) return;
- _ie_syncCond(ed, secId, secPath, idx, kind);
- window._ieRenderOnly();
+function ie_toggleNotLeaf(el, secId, secPath, idx, kind, groupPath, ci) {
+ var c = _ie_condCtx(el, secId, secPath, idx, kind);
+ c.get();
+ c.change();
}
// ── Add/Remove: Steps ──
-function ie_removeStep(secId, secPath, idx, si) {
+function ie_removeStep(el, secId, secPath, idx, si) {
var ed = window._editorData;
if (!ed) return;
ced_syncDOMToData(ed);
@@ -1025,7 +1113,7 @@ function ie_removeStep(secId, secPath, idx, si) {
window._ieRenderCurrent();
}
-function ie_moveStep(secId, secPath, idx, fromSi, toSi) {
+function ie_moveStep(el, secId, secPath, idx, fromSi, toSi) {
var ed = window._editorData;
if (!ed) return;
ced_syncDOMToData(ed);
@@ -1049,62 +1137,54 @@ function ie_moveRow(secPath, fromIdx, toIdx) {
window._ieRenderOnly();
}
-function ie_moveMessage(secId, secPath, idx, si, fromMi, toMi) {
- var ed = window._editorData;
- if (!ed) return;
- ced_syncDOMToData(ed);
- ie_syncStep(ed, secId, secPath, idx, si);
- var step = ed[secPath] && ed[secPath][idx] && ed[secPath][idx].steps && ed[secPath][idx].steps[si];
+function ie_moveMessage(el, secId, secPath, idx, si, fromMi, toMi) {
+ var c = _ie_actCtx(el, secId, secPath, idx, si);
+ var step = c.get();
if (!step || !Array.isArray(step.messages)) return;
if (fromMi < 0 || fromMi >= step.messages.length || toMi < 0 || toMi >= step.messages.length) return;
var m = step.messages.splice(fromMi, 1)[0];
step.messages.splice(toMi, 0, m);
- window._ieRenderOnly();
+ c.set(step);
+ c.change();
}
// ── Add/Remove: Action effects ──
-function ie_addActEffect(secId, secPath, idx, si, effectKey) {
- var ed = window._editorData;
- if (!ed) return;
- ie_syncStep(ed, secId, secPath, idx, si);
- if (!ed[secPath]) ed[secPath] = [];
- if (!ed[secPath][idx]) ed[secPath][idx] = {};
- if (!ed[secPath][idx].steps) ed[secPath][idx].steps = [];
- if (!ed[secPath][idx].steps[si]) ed[secPath][idx].steps[si] = {};
- var step = ed[secPath][idx].steps[si];
-
+function ie_addActEffect(el, secId, secPath, idx, si, effectKey) {
+ var c = _ie_actCtx(el, secId, secPath, idx, si);
+ var step = c.get();
var at = IE_ACT_TYPES.find(function(a) { return a.key === effectKey; });
if (!at) return;
- if (at.kind === 'kv') {
- step[effectKey] = {};
- } else if (at.kind === 'checkbox') {
- step[effectKey] = true;
- } else if (at.kind === 'number') {
- step[effectKey] = 0;
- } else if (at.kind === 'droptable') {
- step[effectKey] = [];
- } else {
- step[effectKey] = '';
- }
- ced_syncDOMToData(ed);
- window._ieRenderOnly();
+ if (at.kind === 'kv') step[effectKey] = {};
+ else if (at.kind === 'checkbox') step[effectKey] = true;
+ else if (at.kind === 'number') step[effectKey] = 0;
+ else if (at.kind === 'droptable') step[effectKey] = [];
+ else step[effectKey] = '';
+ c.set(step);
+ c.change();
}
-function ie_removeActEffect(secId, secPath, idx, si, effectKey) {
- var ed = window._editorData;
- if (!ed) return;
- ie_syncStep(ed, secId, secPath, idx, si);
- var step = ed[secPath] && ed[secPath][idx] && ed[secPath][idx].steps && ed[secPath][idx].steps[si];
- if (!step) return;
+function ie_removeActEffect(el, secId, secPath, idx, si, effectKey) {
+ var c = _ie_actCtx(el, secId, secPath, idx, si);
+ var step = c.get();
delete step[effectKey];
- ced_syncDOMToData(ed);
- window._ieRenderOnly();
+ c.set(step);
+ c.change();
}
// ── Add/Remove: KV row ──
-function ie_addKVRow(secId, secPath, idx, si, effectKey) {
+function ie_addKVRow(el, secId, secPath, idx, si, effectKey) {
+ if (_ie_scopeFromEl(el)) {
+ var c = _ie_actCtx(el, secId, secPath, idx, si);
+ var step = c.get();
+ if (!step[effectKey] || typeof step[effectKey] !== 'object') step[effectKey] = {};
+ step[effectKey][''] = '';
+ c.set(step);
+ c.change();
+ return;
+ }
+
var ed = window._editorData;
if (!ed) return;
ie_syncStep(ed, secId, secPath, idx, si);
@@ -1122,67 +1202,67 @@ function ie_addKVRow(secId, secPath, idx, si, effectKey) {
}
function ie_removeKVRow(rowEl, secId, secPath, idx, si, effectKey) {
- var ed = window._editorData;
- if (!ed) return;
if (rowEl && rowEl.closest) {
var kvRow = rowEl.closest('.ie-kv-row');
if (kvRow) rowEl = kvRow;
}
- ie_syncStep(ed, secId, secPath, idx, si);
- var step = ed[secPath] && ed[secPath][idx] && ed[secPath][idx].steps && ed[secPath][idx].steps[si];
+ var c = _ie_actCtx(rowEl, secId, secPath, idx, si);
+ var step = c.get();
if (!step || !step.hasOwnProperty(effectKey)) return;
var keyEl = rowEl.querySelector('.ie-kv-key');
var k = keyEl ? keyEl.value.trim() : '';
if (k && step[effectKey].hasOwnProperty(k)) delete step[effectKey][k];
- if (Object.keys(step[effectKey]).length === 0) {
- delete step[effectKey];
- }
- ced_syncDOMToData(ed);
- window._ieRenderOnly();
+ if (Object.keys(step[effectKey]).length === 0) delete step[effectKey];
+ c.set(step);
+ c.change();
}
// ── Add/Remove: Messages ──
-function ie_addMessages(secId, secPath, idx, si) {
- var ed = window._editorData;
- if (!ed) return;
- ie_syncStep(ed, secId, secPath, idx, si);
- if (!ed[secPath] || !ed[secPath][idx] || !ed[secPath][idx].steps || !ed[secPath][idx].steps[si]) return;
- var step = ed[secPath][idx].steps[si];
+function ie_addMessages(el, secId, secPath, idx, si) {
+ var c = _ie_actCtx(el, secId, secPath, idx, si);
+ var step = c.get();
if (!step.messages || !Array.isArray(step.messages)) step.messages = [];
step.messages.push('');
- window._ieRenderCurrent();
+ c.set(step);
+ c.change();
}
// ── Drop Table row add/remove ──
-function ie_addDropRow(secId, secPath, idx, si, kind) {
- var ed = window._editorData;
- if (!ed) return;
- ie_syncStep(ed, secId, secPath, idx, si);
- if (!ed[secPath] || !ed[secPath][idx] || !ed[secPath][idx].steps || !ed[secPath][idx].steps[si]) return;
- var step = ed[secPath][idx].steps[si];
+function ie_addDropRow(el, secId, secPath, idx, si, kind) {
+ var c = _ie_actCtx(el, secId, secPath, idx, si);
+ var step = c.get();
if (!Array.isArray(step.drop_table)) step.drop_table = [];
if (kind === 'table') {
step.drop_table.push({table: '', weight: 10, quantity: 1, _kind: 'table'});
} else {
step.drop_table.push({item_id: '', weight: 10, quantity: 1, _kind: 'item'});
}
- window._ieRenderOnly();
+ c.set(step);
+ c.change();
}
-function ie_removeDropRow(secId, secPath, idx, si, ri) {
- var ed = window._editorData;
- if (!ed) return;
- ie_syncStep(ed, secId, secPath, idx, si);
- if (!ed[secPath] || !ed[secPath][idx] || !ed[secPath][idx].steps || !ed[secPath][idx].steps[si]) return;
- var step = ed[secPath][idx].steps[si];
+function ie_removeDropRow(el, secId, secPath, idx, si, ri) {
+ var c = _ie_actCtx(el, secId, secPath, idx, si);
+ var step = c.get();
if (!Array.isArray(step.drop_table)) return;
step.drop_table.splice(ri, 1);
- window._ieRenderOnly();
+ c.set(step);
+ c.change();
}
-function ie_addMessage(secId, secPath, idx, si) {
+function ie_addMessage(el, secId, secPath, idx, si) {
+ if (_ie_scopeFromEl(el)) {
+ var c = _ie_actCtx(el, secId, secPath, idx, si);
+ var step = c.get();
+ if (!step.messages || !Array.isArray(step.messages)) step.messages = [];
+ step.messages.push('');
+ c.set(step);
+ c.change();
+ return;
+ }
+
var ed = window._editorData;
if (!ed) return;
ie_syncStep(ed, secId, secPath, idx, si);
@@ -1201,32 +1281,23 @@ function ie_addMessage(secId, secPath, idx, si) {
}
function ie_removeMessage(rowEl, secId, secPath, idx, si) {
- var ed = window._editorData;
- if (!ed) return;
if (rowEl && rowEl.closest) {
var kvRow = rowEl.closest('.ie-kv-row');
if (kvRow) rowEl = kvRow;
}
- ie_syncStep(ed, secId, secPath, idx, si);
- var step = ed[secPath] && ed[secPath][idx] && ed[secPath][idx].steps && ed[secPath][idx].steps[si];
+ var c = _ie_actCtx(rowEl, secId, secPath, idx, si);
+ var step = c.get();
if (!step || !Array.isArray(step.messages)) return;
-
- var card = document.querySelector('.obj-card[data-card="' + secId + '"]');
- var stepRow = card.querySelector('.obj-sub-row.ie-inter-row[data-idx="' + idx + '"] .ie-step-row[data-step="' + si + '"]');
- if (stepRow) {
- var kvRows = stepRow.querySelectorAll('.ie-act-kv[data-ie-act-key="messages"] .ie-kv-row');
- for (var ri = 0; ri < kvRows.length; ri++) {
- if (kvRows[ri] === rowEl || kvRows[ri].contains(rowEl)) {
- step.messages.splice(ri, 1);
- break;
- }
+ var kvRows = document.querySelectorAll('.ie-act-kv[data-ie-act-key="messages"] .ie-kv-row');
+ for (var ri = 0; ri < kvRows.length; ri++) {
+ if (kvRows[ri] === rowEl || kvRows[ri].contains(rowEl)) {
+ step.messages.splice(ri, 1);
+ break;
}
}
- if (step.messages.length === 0) {
- delete step.messages;
- }
- ced_syncDOMToData(ed);
- window._ieRenderOnly();
+ if (step.messages.length === 0) delete step.messages;
+ c.set(step);
+ c.change();
}
// ── Shared trigger-style array-section renderer ──
diff --git a/internal/admin/static/talktree.js b/internal/admin/static/talktree.js
index 4758918..ef6efd0 100644
--- a/internal/admin/static/talktree.js
+++ b/internal/admin/static/talktree.js
@@ -1,6 +1,7 @@
-// talktree.js — Full-featured conversation tree editor.
-// Inline editing for all TalkNode and TalkOption fields.
-// No prompt() dialogs. Clause-based condition editor, action forms.
+// talktree.js — Conversation tree editor with unified condition & action
+// editors powered by intereditor.js. Tree navigation, messages, and options
+// (text / goto) stay here; condition and action editing delegates to
+// intereditor via talk-scoped contexts that target talk.nodes[key]... paths.
(function() {
'use strict';
@@ -52,103 +53,143 @@
else { delete root[lk]; }
}
- // ---- Clause types ----
-
- var CLAUSE_TYPES = [
- { key: 'flag', label: 'Flag', hasValue: true },
- { key: 'player_flag', label: 'Player Flag', hasValue: true },
- { key: 'has_item', label: 'Has Item', hasValue: false },
- { key: 'min_credits', label: 'Min Credits', hasValue: false }
- ];
+ // ---- Talk scopes for intereditor.js ----
+ // Each scope targets a specific condition or action object inside the talk
+ // tree and handles re-render + talkSync on every mutation.
+
+ // ---- Talk scopes for intereditor.js ----
+ // Each scope targets a specific condition or action object inside the talk
+ // tree and handles re-render + talkSync on every mutation.
+
+ var _talkScopeSeq = 0;
+
+ function talkCondScope(nodeKey, optIdx) {
+ var token = 'tk' + (++_talkScopeSeq);
+ return ie_registerScope({
+ getCond: function() {
+ var t = talkTarget(nodeKey, optIdx);
+ return t && t.condition || null;
+ },
+ setCond: function(c) {
+ var t = talkTarget(nodeKey, optIdx);
+ if (!t) return;
+ if (c) t.condition = c; else delete t.condition;
+ },
+ onChange: function() {
+ talkSync();
+ renderTalkTree(window._talkData);
+ }
+ }, token);
+ }
+
+ function talkActionScope(nodeKey, optIdx) {
+ var token = 'tk' + (++_talkScopeSeq);
+ return ie_registerScope({
+ getStep: function() {
+ var t = talkTarget(nodeKey, optIdx);
+ return t && t.action || {};
+ },
+ setStep: function(s) {
+ var t = talkTarget(nodeKey, optIdx);
+ if (!t) return;
+ if (s && Object.keys(s).length > 0) t.action = s; else delete t.action;
+ },
+ onChange: function() {
+ talkSync();
+ renderTalkTree(window._talkData);
+ }
+ }, token);
+ }
- function clauseTypeLabel(key) {
- for (var i = 0; i < CLAUSE_TYPES.length; i++) {
- if (CLAUSE_TYPES[i].key === key) return CLAUSE_TYPES[i].label;
+ function talkTarget(nodeKey, optIdx) {
+ var node = talkNodes() && talkNodes()[nodeKey];
+ if (!node) return null;
+ if (optIdx !== null) {
+ if (!node.options || !node.options[optIdx]) return null;
+ return node.options[optIdx];
}
- return key;
+ return node;
}
- function clauseTypeHasValue(key) {
- for (var i = 0; i < CLAUSE_TYPES.length; i++) {
- if (CLAUSE_TYPES[i].key === key) return CLAUSE_TYPES[i].hasValue;
- }
- return false;
+ // ---- Render a single action step for a talk node or option ----
+
+ function talkRenderActionStep(nodeKey, optIdx) {
+ var t = talkTarget(nodeKey, optIdx);
+ var step = (t && t.action) || {};
+ var scopeToken = talkActionScope(nodeKey, optIdx);
+ var h = '<div class="ie-act-root" data-ie-scope="' + escAttr(scopeToken) + '">';
+ h += ie_renderMessages(step, '', 0, 0, '');
+ h += ie_renderAct(step, '', 0, 0, '');
+ h += '<div class="ie-act-addbar">' + ie_renderStepAddbar(step, '', 0, 0, '') + '</div>';
+ h += '</div>';
+ return h;
}
- // Extract clauses from a condition object
- function extractClauses(cond) {
- if (!cond || typeof cond !== 'object' || Array.isArray(cond)) return { mode: 'none', clauses: [], groupKey: null };
- if (cond.all_of && Array.isArray(cond.all_of) && cond.all_of.length > 0) {
- return { mode: 'multi', clauses: cond.all_of.slice(), groupKey: 'all_of' };
- }
- if (cond.any_of && Array.isArray(cond.any_of) && cond.any_of.length > 0) {
- return { mode: 'multi', clauses: cond.any_of.slice(), groupKey: 'any_of' };
- }
- // Single clause from flat fields
- for (var i = 0; i < CLAUSE_TYPES.length; i++) {
- if (cond[CLAUSE_TYPES[i].key] !== undefined) {
- var c = {};
- c[CLAUSE_TYPES[i].key] = cond[CLAUSE_TYPES[i].key];
- if (cond.not) c.not = cond.not;
- if (cond.value !== undefined) c.value = cond.value;
- if (cond.has_item && CLAUSE_TYPES[i].key !== 'has_item') c.has_item = cond.has_item;
- if (cond.player_flag && CLAUSE_TYPES[i].key !== 'player_flag') c.player_flag = cond.player_flag;
- if (cond.flag && CLAUSE_TYPES[i].key !== 'flag') c.flag = cond.flag;
- return { mode: 'single', clauses: [c], groupKey: null };
- }
- }
- // Check for has_item or min_credits as the only key
- for (var j = 0; j < CLAUSE_TYPES.length; j++) {
- var ck = CLAUSE_TYPES[j].key;
- if (cond[ck] !== undefined) {
- var cc = {};
- cc[ck] = cond[ck];
- if (cond.not) cc.not = cond.not;
- if (cond.value !== undefined) cc.value = cond.value;
- return { mode: 'single', clauses: [cc], groupKey: null };
- }
- }
- return { mode: 'none', clauses: [], groupKey: null };
+ // ---- talk cond/act add/remove helpers (called from onclick data-action) ----
+
+ function talkAddNodeCond(nodeKey) {
+ var node = talkNodes() && talkNodes()[nodeKey];
+ if (!node || (node.condition && typeof node.condition === 'object')) return;
+ node.condition = {all_of: []};
+ renderTalkTree(window._talkData);
+ talkSync();
}
- // Determine the type key for a clause object
- function clauseObjType(clause) {
- for (var i = 0; i < CLAUSE_TYPES.length; i++) {
- var k = CLAUSE_TYPES[i].key;
- if (clause[k] !== undefined) return k;
- }
- return null;
+ function talkRemoveNodeCond(nodeKey) {
+ var node = talkNodes() && talkNodes()[nodeKey];
+ if (!node) return;
+ delete node.condition;
+ renderTalkTree(window._talkData);
+ talkSync();
}
- // Build a full condition object from clauses
- function buildCondition(clauses, groupKey) {
- if (!clauses || clauses.length === 0) return null;
- if (clauses.length === 1) {
- var flat = {};
- var c = clauses[0];
- for (var k in c) { if (c[k] !== undefined) flat[k] = c[k]; }
- return flat;
- }
- var cond = {};
- cond[groupKey || 'all_of'] = clauses.map(function(c) {
- var sub = {};
- for (var k in c) { if (c[k] !== undefined) sub[k] = c[k]; }
- return sub;
- });
- return cond;
+ function talkAddNodeAct(nodeKey) {
+ var node = talkNodes() && talkNodes()[nodeKey];
+ if (!node || (node.action && typeof node.action === 'object')) return;
+ node.action = {};
+ renderTalkTree(window._talkData);
+ talkSync();
}
- // ---- Action field definitions ----
+ function talkRemoveNodeAct(nodeKey) {
+ var node = talkNodes() && talkNodes()[nodeKey];
+ if (!node) return;
+ delete node.action;
+ renderTalkTree(window._talkData);
+ talkSync();
+ }
+
+ function talkAddOptionCond(nodeKey, optIdx) {
+ var t = talkTarget(nodeKey, optIdx);
+ if (!t || (t.condition && typeof t.condition === 'object')) return;
+ t.condition = {all_of: []};
+ renderTalkTree(window._talkData);
+ talkSync();
+ }
+
+ function talkRemoveOptionCond(nodeKey, optIdx) {
+ var t = talkTarget(nodeKey, optIdx);
+ if (!t) return;
+ delete t.condition;
+ renderTalkTree(window._talkData);
+ talkSync();
+ }
- var ACTION_FIELDS = [
- { key: 'give_item', label: 'Give Item', type: 'item_search' },
- { key: 'take_item', label: 'Take Item', type: 'item_search' },
- { key: 'credits', label: 'Credits (+/-)', type: 'number' },
- { key: 'teleport', label: 'TP Room', type: 'number' },
- { key: 'heal', label: 'Heal HP', type: 'number' },
- ];
+ function talkAddOptionAct(nodeKey, optIdx) {
+ var t = talkTarget(nodeKey, optIdx);
+ if (!t || (t.action && typeof t.action === 'object')) return;
+ t.action = {};
+ renderTalkTree(window._talkData);
+ talkSync();
+ }
- var ACTION_BOOLS = [];
+ function talkRemoveOptionAct(nodeKey, optIdx) {
+ var t = talkTarget(nodeKey, optIdx);
+ if (!t) return;
+ delete t.action;
+ renderTalkTree(window._talkData);
+ talkSync();
+ }
// ---- Event delegation ----
@@ -184,9 +225,6 @@
var nodeKey = el.getAttribute('data-nk');
var optIdx = el.getAttribute('data-oi');
- var subIdx = el.getAttribute('data-si');
- var subKey = el.getAttribute('data-sk');
- var condGroupKey = el.getAttribute('data-cgk');
switch (action) {
case 'delete-node':
@@ -207,32 +245,29 @@
case 'delete-message':
deleteMessageLine(nodeKey, parseInt(optIdx, 10));
break;
- case 'add-condition':
- toggleCondition(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null);
- break;
- case 'remove-condition':
- removeCondition(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null);
+ case 'add-node-cond':
+ talkAddNodeCond(nodeKey);
break;
- case 'add-action':
- addAction(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null);
+ case 'remove-node-cond':
+ talkRemoveNodeCond(nodeKey);
break;
- case 'remove-action':
- removeAction(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null);
+ case 'add-node-act':
+ talkAddNodeAct(nodeKey);
break;
- case 'add-clause':
- addClause(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null, el.getAttribute('data-ct'));
+ case 'remove-node-act':
+ talkRemoveNodeAct(nodeKey);
break;
- case 'remove-clause':
- removeClause(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null, parseInt(subIdx, 10));
+ case 'add-opt-cond':
+ talkAddOptionCond(nodeKey, parseInt(optIdx, 10));
break;
- case 'set-cond-group':
- setCondGroup(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null, condGroupKey);
+ case 'remove-opt-cond':
+ talkRemoveOptionCond(nodeKey, parseInt(optIdx, 10));
break;
- case 'add-kv':
- addKVPair(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null, el.getAttribute('data-kvk'));
+ case 'add-opt-act':
+ talkAddOptionAct(nodeKey, parseInt(optIdx, 10));
break;
- case 'remove-kv':
- removeKVPair(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null, el.getAttribute('data-kvk'), subKey);
+ case 'remove-opt-act':
+ talkRemoveOptionAct(nodeKey, parseInt(optIdx, 10));
break;
}
}
@@ -242,6 +277,13 @@
window.renderTalkTree = function(data) {
var container = document.getElementById('talktree');
if (!container) return;
+ // Purge old talk scopes before re-rendering (new ones will be registered).
+ if (window._ieScopes) {
+ Object.keys(window._ieScopes).forEach(function(k) {
+ if (k.indexOf('tk') === 0) delete window._ieScopes[k];
+ });
+ }
+ _talkScopeSeq = 0;
window._talkData = data;
window._talkOpenNodes = window._talkOpenNodes || new Set();
window._talkOpenNodes.add('start');
@@ -351,7 +393,6 @@
var html = '<div class="talk-node' + (isOpen ? ' open' : '') + '" id="node-' + escAttr(rootKey) + '">';
- // Header — fix: check for data-action before toggling
html += '<div class="talk-node-header" onclick="if(!event.target.closest(\'[data-action]\'))talkToggleNode(\'' + escAttr(rootKey) + '\',this.parentElement)">';
html += '<span class="talk-node-arrow"></span>';
html += '<span class="talk-node-key">' + esc(rootKey) + '</span>';
@@ -360,12 +401,10 @@
html += '<button class="talk-node-btn talk-node-del" data-action="delete-node" data-nk="' + escAttr(rootKey) + '">Delete</button>';
html += '</div>';
- // Body
html += '<div class="talk-node-body">';
html += renderNodeBody(rootKey, node, nodes);
- html += '</div>'; // .talk-node-body
+ html += '</div>';
- // Children
var childKeys = [];
if (node.goto && nodes[node.goto]) childKeys.push(node.goto);
if (node.options) {
@@ -379,7 +418,7 @@
html += '</div>';
}
- html += '</div>'; // .talk-node
+ html += '</div>';
return html;
}
@@ -388,59 +427,60 @@
return '';
}
- // ---- Node body: condition-gated content ----
+ // ---- Node body: condition, messages, options, action via intereditor ----
- // Track open node state across re-renders
window._talkOpenNodes = window._talkOpenNodes || new Set();
function renderNodeBody(nodeKey, node, nodes) {
- var ec = extractClauses(node.condition);
- var hasCond = ec.mode !== 'none' || (node.condition && typeof node.condition === 'object' && !Array.isArray(node.condition));
+ var hasCond = !!(node.condition && typeof node.condition === 'object');
+ var hasAct = !!(node.action && typeof node.action === 'object' && !Array.isArray(node.action));
var hasOpts = node.options && node.options.length > 0;
var html = '';
+ // Node condition (via intereditor)
if (hasCond) {
html += '<div class="talk-cond-card">';
html += '<div class="talk-cond-card-header">' +
- 'Condition' +
- '<button class="talk-section-remove" data-action="remove-condition" data-nk="' + escAttr(nodeKey) + '">Remove</button>' +
+ 'Condition (for this node)' +
+ '<button class="talk-section-remove" data-action="remove-node-cond" data-nk="' + escAttr(nodeKey) + '">Remove</button>' +
'</div>';
html += '<div class="talk-cond-card-body">';
- html += renderConditionClauses(ec, nodeKey, null);
- html += '<div class="talk-cond-card-sep"></div>';
- html += renderMessagesSection(nodeKey, node);
- html += renderOptionsSection(nodeKey, node);
- html += '<div class="talk-cond-card-sep"></div>';
+ html += ie_renderCond(node.condition, '', 0, '', '', {scopeToken: talkCondScope(nodeKey, null), alwaysOuterAddbar: true});
+ html += '</div>';
+ html += '</div>';
+ } else {
+ html += '<button class="talk-add-cond-btn" data-action="add-node-cond" data-nk="' + escAttr(nodeKey) + '">+ Add Condition</button>';
+ }
- var nodeAction = node.action;
- var actionPrefix = 'nodes.' + escAttr(nodeKey) + '.action';
- if (nodeAction && typeof nodeAction === 'object' && !Array.isArray(nodeAction)) {
- html += '<div class="talk-cond-card" style="margin-top:4px">';
- html += '<div class="talk-cond-card-header">' +
- '<span class="talk-prop-label">Action</span>' +
- '<button class="talk-section-remove" data-action="remove-action" data-nk="' + escAttr(nodeKey) + '">Remove</button></div>';
- html += '<div class="talk-cond-card-body">';
- html += renderActionForm(nodeAction, actionPrefix, nodeKey, null);
- html += '</div>';
- html += '</div>';
- } else {
- html += '<button class="talk-add-act-btn" style="margin-top:4px" data-action="add-action" data-nk="' + escAttr(nodeKey) + '">+ Add Action</button>';
- }
+ // Node messages (unchanged)
+ html += renderMessagesSection(nodeKey, node);
- html += '<div class="talk-cond-card-sep"></div>';
- html += renderGotoFallback(nodeKey, node, 'If condition fails, go to');
- html += '</div>'; // .talk-cond-card-body
- html += '</div>'; // .talk-cond-card
+ // Node options
+ html += renderOptionsSection(nodeKey, node);
+
+ html += '<div class="talk-cond-card-sep"></div>';
+
+ // Node action (via intereditor)
+ if (hasAct) {
+ html += '<div class="talk-cond-card" style="margin-top:4px">';
+ html += '<div class="talk-cond-card-header">' +
+ 'Action (at the start of this node)' +
+ '<button class="talk-section-remove" data-action="remove-node-act" data-nk="' + escAttr(nodeKey) + '">Remove</button></div>';
+ html += '<div class="talk-cond-card-body">';
+ html += talkRenderActionStep(nodeKey, null);
+ html += '</div>';
+ html += '</div>';
} else {
- html += '<button class="talk-add-cond-btn" data-action="add-condition" data-nk="' + escAttr(nodeKey) + '">+ Add Condition</button>';
- html += renderMessagesSection(nodeKey, node);
- html += renderOptionsSection(nodeKey, node);
+ html += '<button class="talk-add-act-btn" style="margin-top:4px" data-action="add-node-act" data-nk="' + escAttr(nodeKey) + '">+ Add Action</button>';
+ }
+
+ // Goto fallback
+ if (hasCond) {
html += '<div class="talk-cond-card-sep"></div>';
- html += renderActionSection(nodeKey, null, node.action);
- if (!hasOpts) {
- html += renderGotoFallback(nodeKey, node, 'Auto-advance to');
- }
+ html += renderGotoFallback(nodeKey, node, 'If condition fails, go to');
+ } else if (!hasOpts) {
+ html += renderGotoFallback(nodeKey, node, 'Auto-advance to');
}
return html;
@@ -478,259 +518,6 @@
return html;
}
- // ---- Condition clauses ----
-
- function renderConditionClauses(ec, nodeKey, optIdx) {
- var clauses = ec.clauses;
- var groupKey = ec.groupKey || 'all_of';
- var html = '';
-
- // AND/OR mode selector (only when 2+ clauses)
- if (clauses.length >= 2) {
- html += '<div class="talk-cond-mode-row">' +
- '<span class="talk-prop-label">Combine with</span>' +
- '<select class="talk-select" data-action="set-cond-group" data-nk="' + escAttr(nodeKey) + '"' + (optIdx !== null ? ' data-oi="' + optIdx + '"' : '') + ' data-cgk="' + escAttr(groupKey) + '" onchange="event.stopPropagation(); talkCondGroupChange(this)">' +
- '<option value="all_of"' + (groupKey === 'all_of' ? ' selected' : '') + '>All of (AND)</option>' +
- '<option value="any_of"' + (groupKey === 'any_of' ? ' selected' : '') + '>Any of (OR)</option>' +
- '</select>' +
- '</div>';
- }
-
- // Clause rows
- for (var i = 0; i < clauses.length; i++) {
- var typeKey = clauseObjType(clauses[i]) || 'flag';
- var pathPrefix = clauses.length === 1 ?
- 'nodes.' + escAttr(nodeKey) + (optIdx !== null ? '.options.' + optIdx : '') + '.condition' :
- 'nodes.' + escAttr(nodeKey) + (optIdx !== null ? '.options.' + optIdx : '') + '.condition.' + groupKey + '.' + i;
- html += renderClauseRow(clauses[i], pathPrefix, typeKey, nodeKey, optIdx, i);
- }
-
- // Add clause buttons
- html += '<div class="talk-clause-add-bar">';
- CLAUSE_TYPES.forEach(function(ct) {
- html += '<button class="talk-clause-add-btn" data-action="add-clause" data-nk="' + escAttr(nodeKey) + '"' +
- (optIdx !== null ? ' data-oi="' + optIdx + '"' : '') + ' data-ct="' + escAttr(ct.key) + '">+ ' + esc(ct.label) + '</button>';
- });
- html += '</div>';
-
- return html;
- }
-
- function renderClauseRow(clause, pathPrefix, typeKey, nodeKey, optIdx, clauseIdx) {
- var hasValue = clauseTypeHasValue(typeKey);
- var html = '<div class="talk-clause-row">';
- html += '<span class="talk-clause-type">' + esc(clauseTypeLabel(typeKey)) + '</span>';
-
- if (typeKey === 'flag' || typeKey === 'player_flag') {
- var notLabel = (clause.value !== undefined && clause.value !== '') ? 'Not' : 'Not Exists';
- html += '<span class="talk-clause-label">key:</span>';
- html += '<input class="talk-input talk-input-sm" data-tp="' + escAttr(pathPrefix + '.' + typeKey) + '" value="' + escAttr(clause[typeKey] || '') + '" placeholder="Flag name...">';
- html += '<label class="talk-check-label"><input type="checkbox" data-tp="' + escAttr(pathPrefix + '.not') + '"' + (clause.not ? ' checked' : '') + '> ' + notLabel + '</label>';
- html += '<span class="talk-clause-label">val:</span>';
- html += '<input class="talk-input talk-input-sm" data-tp="' + escAttr(pathPrefix + '.value') + '" value="' + escAttr(clause.value || '') + '" placeholder="Value match..." style="max-width:80px">';
- } else if (typeKey === 'has_item') {
- html += '<span class="talk-clause-label">item:</span>';
- html += '<input class="talk-input talk-input-sm" data-tp="' + escAttr(pathPrefix + '.has_item') + '" value="' + escAttr(clause.has_item || '') + '" placeholder="Item ID...">';
- html += '<label class="talk-check-label"><input type="checkbox" data-tp="' + escAttr(pathPrefix + '.not') + '"' + (clause.not ? ' checked' : '') + '> Not Exists</label>';
- } else if (typeKey === 'min_credits') {
- html += '<span class="talk-clause-label">cr:</span>';
- html += '<input class="talk-input-num" data-tp="' + escAttr(pathPrefix + '.min_credits') + '" type="number" value="' + (clause.min_credits || 0) + '" placeholder="0">';
- html += '<label class="talk-check-label"><input type="checkbox" data-tp="' + escAttr(pathPrefix + '.not') + '"' + (clause.not ? ' checked' : '') + '> Not</label>';
- }
-
- html += '<button class="talk-clause-del" data-action="remove-clause" data-nk="' + escAttr(nodeKey) + '"' + (optIdx !== null ? ' data-oi="' + optIdx + '"' : '') + ' data-si="' + clauseIdx + '" title="Remove clause">x</button>';
- html += '</div>';
- return html;
- }
-
- // ---- Clause add/remove/migrate ----
-
- function getConditionFor(nodeKey, optIdx) {
- var node = talkNodes()[nodeKey];
- if (!node) return null;
- if (optIdx !== null) {
- if (!node.options || !node.options[optIdx]) return null;
- return node.options[optIdx];
- }
- return node;
- }
-
- function condAddClause(target, typeKey, optIdx, nodeKey) {
- var ec = extractClauses(target.condition);
- var newClause = {};
- newClause[typeKey] = '';
- ec.clauses.push(newClause);
-
- if (ec.clauses.length === 1) {
- // First clause: use flat fields
- target.condition = buildCondition(ec.clauses, null);
- } else {
- ec.groupKey = ec.groupKey || 'all_of';
- target.condition = buildCondition(ec.clauses, ec.groupKey);
- }
- }
-
- function addClause(nodeKey, optIdx, typeKey) {
- var target = getConditionFor(nodeKey, optIdx);
- if (!target) return;
- if (!target.condition || typeof target.condition !== 'object' || Array.isArray(target.condition)) {
- target.condition = {};
- }
- condAddClause(target, typeKey, optIdx, nodeKey);
- renderTalkTree(window._talkData);
- talkSync();
- }
-
- function removeClause(nodeKey, optIdx, clauseIdx) {
- var target = getConditionFor(nodeKey, optIdx);
- if (!target) return;
- var ec = extractClauses(target.condition);
- if (clauseIdx < 0 || clauseIdx >= ec.clauses.length) return;
-
- ec.clauses.splice(clauseIdx, 1);
-
- if (ec.clauses.length === 0) {
- delete target.condition;
- } else {
- target.condition = buildCondition(ec.clauses, ec.groupKey);
- }
- renderTalkTree(window._talkData);
- talkSync();
- }
-
- function setCondGroup(nodeKey, optIdx, newGroupKey) {
- var target = getConditionFor(nodeKey, optIdx);
- if (!target) return;
- var ec = extractClauses(target.condition);
- if (ec.clauses.length < 2) return;
- ec.groupKey = newGroupKey;
- target.condition = buildCondition(ec.clauses, ec.groupKey);
- renderTalkTree(window._talkData);
- talkSync();
- }
-
- // Called from onchange on the AND/OR select
- window.talkCondGroupChange = function(select) {
- var nodeKey = select.getAttribute('data-nk');
- var optIdx = select.getAttribute('data-oi');
- setCondGroup(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null, select.value);
- };
-
- // ---- Add/Remove whole Condition ----
-
- function toggleCondition(nodeKey, optIdx) {
- var target = getConditionFor(nodeKey, optIdx);
- if (!target) return;
- if (target.condition && typeof target.condition === 'object' && !Array.isArray(target.condition)) {
- delete target.condition;
- } else {
- target.condition = {};
- if (optIdx !== null) {
- window._talkAutoOpenDetails = new Set();
- window._talkAutoOpenDetails.add('cond_' + optIdx + '_' + escAttr(nodeKey));
- }
- }
- renderTalkTree(window._talkData);
- talkSync();
- }
-
- function removeCondition(nodeKey, optIdx) {
- var target = getConditionFor(nodeKey, optIdx);
- if (!target) return;
- delete target.condition;
- renderTalkTree(window._talkData);
- talkSync();
- }
-
- // ---- Action editor ----
-
- function renderActionSection(nodeKey, optIdx, action) {
- var prefix = optIdx !== null ? 'nodes.' + escAttr(nodeKey) + '.options.' + optIdx + '.action' : 'nodes.' + escAttr(nodeKey) + '.action';
- var hasAct = action && typeof action === 'object' && !Array.isArray(action);
-
- if (!hasAct) {
- return '<div class="talk-section">' +
- '<button class="talk-add-act-btn" data-action="add-action" data-nk="' + escAttr(nodeKey) + '"' + (optIdx !== null ? ' data-oi="' + optIdx + '"' : '') + '>+ Add Action</button>' +
- '</div>';
- }
-
- return '<div class="talk-section">' +
- '<div class="talk-cond-card-header">' +
- '<span class="talk-prop-label">Action</span>' +
- '<button class="talk-section-remove" data-action="remove-action" data-nk="' + escAttr(nodeKey) + '"' + (optIdx !== null ? ' data-oi="' + optIdx + '"' : '') + '>Remove</button>' +
- '</div>' +
- renderActionForm(action, prefix, nodeKey, optIdx) +
- '</div>';
- }
-
- function renderActionForm(action, prefix, nodeKey, optIdx) {
- var html = '<div class="talk-action-block">';
-
- html += '<div class="talk-action-grid">';
- ACTION_FIELDS.forEach(function(f) {
- var fpath = prefix + '.' + f.key;
- var val = (action && action[f.key] !== undefined) ? action[f.key] : '';
- if (f.type === 'item_search') {
- html += '<div class="talk-field-row">' +
- '<span class="talk-prop-label">' + esc(f.label) + '</span>' +
- '<div style="position:relative">' +
- '<input class="talk-input talk-input-sm search-input" data-tp="' + escAttr(fpath) + '" data-search-type="items" value="' + escAttr(String(val)) + '" placeholder="Search items...">' +
- '<div class="search-results" style="display:none"></div>' +
- '</div>' +
- '</div>';
- } else {
- var narrowStyle = (f.key === 'teleport' || f.key === 'heal') ? ' style="max-width:70px"' : '';
- html += '<div class="talk-field-row">' +
- '<span class="talk-prop-label">' + esc(f.label) + '</span>' +
- '<input class="talk-input talk-input-sm"' + narrowStyle + ' data-tp="' + escAttr(fpath) + '" type="' + f.type + '" value="' + escAttr(String(val)) + '">' +
- '</div>';
- }
- });
- html += '</div>';
-
- html += '<div style="margin-top:4px">';
- ACTION_BOOLS.forEach(function(b) {
- html += '<label class="talk-check-label">' +
- '<input type="checkbox" data-tp="' + escAttr(prefix + '.' + b.key) + '"' + (action && action[b.key] ? ' checked' : '') + '> ' + esc(b.label) +
- '</label>';
- });
- html += '</div>';
-
- html += renderKVEditor(action, prefix, 'set_flags', 'Set Flags', nodeKey, optIdx);
- html += renderKVEditor(action, prefix, 'set_player_flags', 'Set Player Flags', nodeKey, optIdx);
-
- html += '</div>'; // .talk-action-block
- return html;
- }
-
- function renderKVEditor(action, prefix, key, label, nodeKey, optIdx) {
- var kv = (action && action[key]) || {};
- var keys = Object.keys(kv);
- var fcID = prefix.replace(/\./g, '_') + '_' + key;
- var addKeyID = 'kvAddKey_' + fcID;
- var nkAttr = escAttr(nodeKey);
- var oiAttr = optIdx !== null ? ' data-oi="' + optIdx + '"' : '';
- var html = '<div class="talk-kv-section">';
- html += '<div class="talk-kv-header">';
- html += '<span class="talk-prop-label">' + esc(label) + '</span>';
- html += '</div>';
- keys.forEach(function(k) {
- html += '<div class="talk-kv-row">' +
- '<input class="talk-input" data-nk="' + nkAttr + '"' + oiAttr + ' data-kvk="' + escAttr(key) + '" data-oldkey="' + escAttr(k) + '" onchange="talkRenameKV(this)" value="' + escAttr(k) + '" placeholder="key" style="max-width:150px">' +
- '<input class="talk-input" data-tp="' + escAttr(prefix + '.' + key + '.' + k) + '" value="' + escAttr(String(kv[k])) + '" placeholder="value">' +
- '<button class="talk-kv-del" data-action="remove-kv" data-nk="' + nkAttr + '"' + oiAttr + ' data-kvk="' + escAttr(key) + '" data-sk="' + escAttr(k) + '">x</button>' +
- '</div>';
- });
- html += '<div class="talk-kv-row">' +
- '<input class="talk-input" id="' + escAttr(addKeyID) + '" placeholder="New key name" style="max-width:150px" data-nk="' + nkAttr + '"' + oiAttr + ' data-kvk="' + escAttr(key) + '">' +
- '<input class="talk-input" id="' + escAttr(addKeyID) + '_val" placeholder="value" style="flex:1">' +
- '<span style="font-size:9px;color:#888;white-space:nowrap;min-width:60px"></span>' +
- '<button class="talk-kv-add" data-action="add-kv" data-nk="' + nkAttr + '"' + oiAttr + ' data-kvk="' + escAttr(key) + '">Add</button>' +
- '</div>';
- html += '</div>';
- return html;
- }
-
// ---- Options section ----
function renderOptionsSection(nodeKey, node) {
@@ -747,43 +534,42 @@
html += '<div class="talk-opt-edit-actions">';
if (opt.condition && typeof opt.condition === 'object' && !Array.isArray(opt.condition)) {
- html += '<span class="talk-opt-tag talk-opt-tag-cond" title="Has condition" onclick="toggleOptionDetail(event,\'cond\',' + idx + ',\'' + escAttr(nodeKey) + '\')">cond</span>';
+ html += '<span class="talk-opt-tag talk-opt-tag-cond">cond</span>';
} else {
- html += '<button class="talk-add-cond-btn" style="margin:0;margin-bottom:0" data-action="add-condition" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '" title="Add condition">cond</button>';
+ html += '<button class="talk-add-cond-btn" style="margin:0;margin-bottom:0" data-action="add-opt-cond" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '" title="Add condition">cond</button>';
}
if (opt.action) {
- html += '<span class="talk-opt-tag talk-opt-tag-act" title="Has action" onclick="toggleOptionDetail(event,\'act\',' + idx + ',\'' + escAttr(nodeKey) + '\')">act</span>';
+ html += '<span class="talk-opt-tag talk-opt-tag-act">act</span>';
} else {
- html += '<button class="talk-add-act-btn" style="margin:0;margin-bottom:0" data-action="add-action" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '" title="Add action">act</button>';
+ html += '<button class="talk-add-act-btn" style="margin:0;margin-bottom:0" data-action="add-opt-act" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '" title="Add action">act</button>';
}
html += '</div>';
html += '<button class="talk-opt-del" data-action="delete-option" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '" title="Delete option">x</button>';
html += '</div>';
+ // Option condition detail
if (opt.condition && typeof opt.condition === 'object' && !Array.isArray(opt.condition)) {
- var condDetailKey = 'cond_' + idx + '_' + escAttr(nodeKey);
- var condOpen = window._talkAutoOpenDetails && window._talkAutoOpenDetails.has(condDetailKey) ? 'block' : 'none';
- html += '<div class="talk-opt-detail" id="optDetail_' + condDetailKey + '" style="display:' + condOpen + ';margin-left:18px">';
+ html += '<div class="talk-opt-detail" style="display:block;margin-left:18px">';
html += '<div class="talk-cond-card" style="margin-top:2px">';
- html += '<div class="talk-cond-card-header">Condition' +
- '<button class="talk-section-remove" data-action="remove-condition" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '">Remove</button></div>';
+ html += '<div class="talk-cond-card-header">Condition (for this option to show up)' +
+ '<button class="talk-section-remove" data-action="remove-opt-cond" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '">Remove</button></div>';
html += '<div class="talk-cond-card-body">';
- html += renderConditionClauses(extractClauses(opt.condition), nodeKey, idx);
+ html += ie_renderCond(opt.condition, '', 0, '', '', {scopeToken: talkCondScope(nodeKey, idx), alwaysOuterAddbar: true});
html += '</div>';
html += '</div>';
html += '</div>';
}
+
+ // Option action detail
if (opt.action) {
- var actDetailKey = 'act_' + idx + '_' + escAttr(nodeKey);
- var actOpen = window._talkAutoOpenDetails && window._talkAutoOpenDetails.has(actDetailKey) ? 'block' : 'none';
- html += '<div class="talk-opt-detail" id="optDetail_' + actDetailKey + '" style="display:' + actOpen + ';margin-left:18px">';
+ html += '<div class="talk-opt-detail" style="display:block;margin-left:18px">';
html += '<div class="talk-cond-card" style="margin-top:2px">';
html += '<div class="talk-cond-card-header">' +
- '<span class="talk-prop-label">Action</span>' +
- '<button class="talk-section-remove" data-action="remove-action" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '">Remove</button></div>';
+ 'Action (when this option is chosen)' +
+ '<button class="talk-section-remove" data-action="remove-opt-act" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '">Remove</button></div>';
html += '<div class="talk-cond-card-body">';
- html += renderActionForm(opt.action, 'nodes.' + escAttr(nodeKey) + '.options.' + idx + '.action', nodeKey, idx);
+ html += talkRenderActionStep(nodeKey, idx);
html += '</div>';
html += '</div>';
html += '</div>';
@@ -801,16 +587,10 @@
'<button class="talk-add-btn" data-action="add-option" data-nk="' + escAttr(nodeKey) + '">Add</button>' +
'</div>';
- html += '</div>'; // .talk-options
+ html += '</div>';
return html;
}
- window.toggleOptionDetail = function(e, type, idx, nodeKey) {
- e.stopPropagation();
- var el = document.getElementById('optDetail_' + type + '_' + idx + '_' + nodeKey);
- if (el) el.style.display = el.style.display === 'none' ? 'block' : 'none';
- };
-
window.talkToggleNode = function(nodeKey, nodeEl) {
var open = nodeEl.classList.toggle('open');
if (open) {
@@ -867,98 +647,6 @@
talkSync();
}
- // ---- Add/Remove Action ----
-
- function addAction(nodeKey, optIdx) {
- var target = getConditionFor(nodeKey, optIdx);
- if (!target) return;
- target.action = {};
- if (optIdx !== null) {
- window._talkAutoOpenDetails = new Set();
- window._talkAutoOpenDetails.add('act_' + optIdx + '_' + escAttr(nodeKey));
- }
- renderTalkTree(window._talkData);
- talkSync();
- }
-
- function removeAction(nodeKey, optIdx) {
- var target = getConditionFor(nodeKey, optIdx);
- if (!target) return;
- delete target.action;
- renderTalkTree(window._talkData);
- talkSync();
- }
-
- // ---- Add/Remove KV Pairs (set_flags/set_player_flags) ----
-
- function kvPath(nodeKey, optIdx, kvKey, flagKey) {
- var base = optIdx !== null ?
- 'nodes.' + nodeKey + '.options.' + optIdx + '.action.' + kvKey :
- 'nodes.' + nodeKey + '.action.' + kvKey;
- return flagKey ? base + '.' + flagKey : base;
- }
-
- function addKVPair(nodeKey, optIdx, kvKey) {
- var prefixBase = optIdx !== null ?
- 'nodes.' + nodeKey + '.options.' + optIdx + '.action' :
- 'nodes.' + nodeKey + '.action';
- var fcID = prefixBase.replace(/\./g, '_') + '_' + kvKey;
- var addKeyID = 'kvAddKey_' + fcID;
- var input = document.getElementById(addKeyID);
- var key = input ? input.value.trim() : '';
- if (!key) return;
- var valInput = document.getElementById(addKeyID + '_val');
- var rawVal = valInput ? valInput.value.trim() : 'true';
- var val = rawVal === 'true' ? true : rawVal === 'false' ? false : rawVal;
- var numVal = parseFloat(rawVal);
- if (!isNaN(numVal) && String(numVal) === rawVal) val = numVal;
- var basePath = kvPath(nodeKey, optIdx, kvKey);
- var obj = talkGet(basePath);
- if (!obj || typeof obj !== 'object') {
- talkSet(basePath, {});
- obj = talkGet(basePath);
- }
- if (obj[key] !== undefined) { notify('Flag "' + key + '" already exists.', 'error'); return; }
- obj[key] = val;
- renderTalkTree(window._talkData);
- talkSync();
- }
-
- function removeKVPair(nodeKey, optIdx, kvKey, flagKey) {
- var fullPath = kvPath(nodeKey, optIdx, kvKey, flagKey);
- talkDel(fullPath);
- var basePath = kvPath(nodeKey, optIdx, kvKey);
- var obj = talkGet(basePath);
- if (obj && typeof obj === 'object' && Object.keys(obj).length === 0) {
- talkDel(basePath);
- }
- renderTalkTree(window._talkData);
- talkSync();
- }
-
- window.talkRenameKV = function(el) {
- var nodeKey = el.getAttribute('data-nk');
- var optIdxStr = el.getAttribute('data-oi');
- var optIdx = optIdxStr !== null ? parseInt(optIdxStr, 10) : null;
- var kvKey = el.getAttribute('data-kvk');
- var oldKey = el.getAttribute('data-oldkey');
- var newKey = el.value.trim();
- if (!newKey || newKey === oldKey) return;
- var basePath = optIdx !== null ?
- 'nodes.' + nodeKey + '.options.' + optIdx + '.action.' + kvKey :
- 'nodes.' + nodeKey + '.action.' + kvKey;
- var obj = talkGet(basePath);
- if (!obj || obj[newKey] !== undefined) {
- el.value = oldKey;
- if (obj && obj[newKey] !== undefined) notify('Key "' + newKey + '" already exists.', 'error');
- return;
- }
- obj[newKey] = obj[oldKey];
- delete obj[oldKey];
- renderTalkTree(window._talkData);
- talkSync();
- };
-
// ---- Global event listeners ----
document.addEventListener('input', function(e) {
@@ -1004,15 +692,6 @@
addMessageLine(nk);
return;
}
-
- if (el.id && el.id.startsWith('kvAddKey_')) {
- e.preventDefault();
- var nk = el.getAttribute('data-nk');
- var oi = el.getAttribute('data-oi');
- var kvk = el.getAttribute('data-kvk');
- addKVPair(nk, oi !== null ? parseInt(oi, 10) : null, kvk);
- return;
- }
});
})();