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/talktree.js | 731 +++++++++++--------------------------- 1 file changed, 205 insertions(+), 526 deletions(-) (limited to 'internal/admin/static/talktree.js') 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 = '
'; + h += ie_renderMessages(step, '', 0, 0, ''); + h += ie_renderAct(step, '', 0, 0, ''); + h += '
' + ie_renderStepAddbar(step, '', 0, 0, '') + '
'; + h += '
'; + 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 = '
'; - // Header — fix: check for data-action before toggling html += '
'; html += ''; html += '' + esc(rootKey) + ''; @@ -360,12 +401,10 @@ html += ''; html += '
'; - // Body html += '
'; html += renderNodeBody(rootKey, node, nodes); - html += '
'; // .talk-node-body + html += '
'; - // Children var childKeys = []; if (node.goto && nodes[node.goto]) childKeys.push(node.goto); if (node.options) { @@ -379,7 +418,7 @@ html += ''; } - html += ''; // .talk-node + html += ''; 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 += '
'; html += '
' + - 'Condition' + - '' + + 'Condition (for this node)' + + '' + '
'; html += '
'; - html += renderConditionClauses(ec, nodeKey, null); - html += '
'; - html += renderMessagesSection(nodeKey, node); - html += renderOptionsSection(nodeKey, node); - html += '
'; + html += ie_renderCond(node.condition, '', 0, '', '', {scopeToken: talkCondScope(nodeKey, null), alwaysOuterAddbar: true}); + html += '
'; + html += '
'; + } else { + html += ''; + } - var nodeAction = node.action; - var actionPrefix = 'nodes.' + escAttr(nodeKey) + '.action'; - if (nodeAction && typeof nodeAction === 'object' && !Array.isArray(nodeAction)) { - html += '
'; - html += '
' + - 'Action' + - '
'; - html += '
'; - html += renderActionForm(nodeAction, actionPrefix, nodeKey, null); - html += '
'; - html += '
'; - } else { - html += ''; - } + // Node messages (unchanged) + html += renderMessagesSection(nodeKey, node); - html += '
'; - html += renderGotoFallback(nodeKey, node, 'If condition fails, go to'); - html += ''; // .talk-cond-card-body - html += ''; // .talk-cond-card + // Node options + html += renderOptionsSection(nodeKey, node); + + html += '
'; + + // Node action (via intereditor) + if (hasAct) { + html += '
'; + html += '
' + + 'Action (at the start of this node)' + + '
'; + html += '
'; + html += talkRenderActionStep(nodeKey, null); + html += '
'; + html += '
'; } else { - html += ''; - html += renderMessagesSection(nodeKey, node); - html += renderOptionsSection(nodeKey, node); + html += ''; + } + + // Goto fallback + if (hasCond) { html += '
'; - 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 += '
' + - 'Combine with' + - '' + - '
'; - } - - // 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 += '
'; - CLAUSE_TYPES.forEach(function(ct) { - html += ''; - }); - html += '
'; - - return html; - } - - function renderClauseRow(clause, pathPrefix, typeKey, nodeKey, optIdx, clauseIdx) { - var hasValue = clauseTypeHasValue(typeKey); - var html = '
'; - html += '' + esc(clauseTypeLabel(typeKey)) + ''; - - if (typeKey === 'flag' || typeKey === 'player_flag') { - var notLabel = (clause.value !== undefined && clause.value !== '') ? 'Not' : 'Not Exists'; - html += 'key:'; - html += ''; - html += ''; - html += 'val:'; - html += ''; - } else if (typeKey === 'has_item') { - html += 'item:'; - html += ''; - html += ''; - } else if (typeKey === 'min_credits') { - html += 'cr:'; - html += ''; - html += ''; - } - - html += ''; - html += '
'; - 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 '
' + - '' + - '
'; - } - - return '
' + - '
' + - 'Action' + - '' + - '
' + - renderActionForm(action, prefix, nodeKey, optIdx) + - '
'; - } - - function renderActionForm(action, prefix, nodeKey, optIdx) { - var html = '
'; - - html += '
'; - 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 += '
' + - '' + esc(f.label) + '' + - '
' + - '' + - '' + - '
' + - '
'; - } else { - var narrowStyle = (f.key === 'teleport' || f.key === 'heal') ? ' style="max-width:70px"' : ''; - html += '
' + - '' + esc(f.label) + '' + - '' + - '
'; - } - }); - html += '
'; - - html += '
'; - ACTION_BOOLS.forEach(function(b) { - html += ''; - }); - html += '
'; - - html += renderKVEditor(action, prefix, 'set_flags', 'Set Flags', nodeKey, optIdx); - html += renderKVEditor(action, prefix, 'set_player_flags', 'Set Player Flags', nodeKey, optIdx); - - html += '
'; // .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 = '
'; - html += '
'; - html += '' + esc(label) + ''; - html += '
'; - keys.forEach(function(k) { - html += '
' + - '' + - '' + - '' + - '
'; - }); - html += '
' + - '' + - '' + - '' + - '' + - '
'; - html += '
'; - return html; - } - // ---- Options section ---- function renderOptionsSection(nodeKey, node) { @@ -747,43 +534,42 @@ html += '
'; if (opt.condition && typeof opt.condition === 'object' && !Array.isArray(opt.condition)) { - html += 'cond'; + html += 'cond'; } else { - html += ''; + html += ''; } if (opt.action) { - html += 'act'; + html += 'act'; } else { - html += ''; + html += ''; } html += '
'; html += ''; html += ''; + // 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 += '
'; + html += '
'; html += '
'; - html += '
Condition' + - '
'; + html += '
Condition (for this option to show up)' + + '
'; html += '
'; - html += renderConditionClauses(extractClauses(opt.condition), nodeKey, idx); + html += ie_renderCond(opt.condition, '', 0, '', '', {scopeToken: talkCondScope(nodeKey, idx), alwaysOuterAddbar: true}); html += '
'; html += '
'; html += '
'; } + + // Option action detail if (opt.action) { - var actDetailKey = 'act_' + idx + '_' + escAttr(nodeKey); - var actOpen = window._talkAutoOpenDetails && window._talkAutoOpenDetails.has(actDetailKey) ? 'block' : 'none'; - html += '
'; + html += '
'; html += '
'; html += '
' + - 'Action' + - '
'; + 'Action (when this option is chosen)' + + '
'; html += '
'; - html += renderActionForm(opt.action, 'nodes.' + escAttr(nodeKey) + '.options.' + idx + '.action', nodeKey, idx); + html += talkRenderActionStep(nodeKey, idx); html += '
'; html += '
'; html += '
'; @@ -801,16 +587,10 @@ '' + '
'; - html += ''; // .talk-options + html += ''; 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; - } }); })(); -- cgit v1.2.3