From b31958c6304a25279197c1a08b924b6b44798a99 Mon Sep 17 00:00:00 2001
From: historia <[not public]>
Date: Fri, 10 Jul 2026 18:53:26 -0400
Subject: feat(admin): use intereditor.js for mob talk editor to further unify
condition/action system
---
internal/admin/static/intereditor.js | 523 ++++++++++++++++++++---------------
1 file changed, 297 insertions(+), 226 deletions(-)
(limited to 'internal/admin/static/intereditor.js')
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 += '';
+ h += '';
} else {
- h += '';
+ h += '';
}
});
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 += '';
+ h += '';
});
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 += '
';
+ h += '
';
if (condTitleText) h += '
' + esc(condTitleText) + '
';
h += ie_renderGroup(condObj, secId, secPath, idx, '', kind);
h += '
';
}
- var showAddbar = !isGroup || (opts && opts.alwaysOuterAddbar && rootChildCount === 0);
+ var showAddbar = (!isGroup || (opts.alwaysOuterAddbar && rootChildCount === 0)) && typeof condObj === 'object';
if (showAddbar) {
- h += '
';
+ h += '
';
IE_COND_TYPES.forEach(function(ct) {
- h += '';
+ h += '';
});
- h += '';
+ h += '';
h += '
';
}
return h;
@@ -442,10 +570,10 @@ function _ie_renderGroupNode(mode, children, not, secId, secPath, idx, gpath, ki
h += '
';
h += '
';
IE_COND_TYPES.forEach(function(ct) {
- h += '';
+ h += '';
});
- h += '';
- h += '';
+ h += '';
+ h += '';
h += '
';
h += '
';
return h;
@@ -454,13 +582,13 @@ function _ie_renderGroupNode(mode, children, not, secId, secPath, idx, gpath, ki
h += '
';
h += '
';
if (children.length >= 2) {
- h += '
';
h += '
';
@@ -481,9 +609,9 @@ function _ie_renderGroupNode(mode, children, not, secId, secPath, idx, gpath, ki
h += '
';
h += '
';
IE_COND_TYPES.forEach(function(ct) {
- h += '';
+ h += '';
});
- h += '';
+ h += '';
h += '
';
h += '
';
return h;
@@ -516,8 +644,8 @@ function _ie_renderLeaf(leafType, leafVal, leafSub, not, secId, secPath, idx, gp
h += '=';
h += '';
}
- h += '';
- h += '';
+ h += '';
+ h += '';
h += '