aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/static/talktree.js
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-01 16:37:45 -0400
committerhistoria <[not public]>2026-07-01 16:37:45 -0400
commite6b4cb9f5816c6ee239233bc85f74ee446a161c2 (patch)
tree1c3bf71c8b7f095baae989afe5e33183a7ed3929 /internal/admin/static/talktree.js
parent649ef4b7416ce7053145878841fc9b0bff2f5fec (diff)
downloadthehouseoficarus-e6b4cb9f5816c6ee239233bc85f74ee446a161c2.tar.gz
feat: shop system overhauled, gui conversation tree editor overhauled
Diffstat (limited to 'internal/admin/static/talktree.js')
-rw-r--r--internal/admin/static/talktree.js330
1 files changed, 170 insertions, 160 deletions
diff --git a/internal/admin/static/talktree.js b/internal/admin/static/talktree.js
index e7079f5..4758918 100644
--- a/internal/admin/static/talktree.js
+++ b/internal/admin/static/talktree.js
@@ -86,11 +86,11 @@
}
// Single clause from flat fields
for (var i = 0; i < CLAUSE_TYPES.length; i++) {
- if (cond[CLAUSE_TYPES[i].key] !== undefined && cond[CLAUSE_TYPES[i].key] !== '') {
+ 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 && cond.value !== '') c.value = cond.value;
+ 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;
@@ -100,11 +100,11 @@
// 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 && cond[ck] !== '') {
+ if (cond[ck] !== undefined) {
var cc = {};
cc[ck] = cond[ck];
if (cond.not) cc.not = cond.not;
- if (cond.value !== undefined && cond.value !== '') cc.value = cond.value;
+ if (cond.value !== undefined) cc.value = cond.value;
return { mode: 'single', clauses: [cc], groupKey: null };
}
}
@@ -115,7 +115,7 @@
function clauseObjType(clause) {
for (var i = 0; i < CLAUSE_TYPES.length; i++) {
var k = CLAUSE_TYPES[i].key;
- if (clause[k] !== undefined && clause[k] !== '') return k;
+ if (clause[k] !== undefined) return k;
}
return null;
}
@@ -126,13 +126,13 @@
if (clauses.length === 1) {
var flat = {};
var c = clauses[0];
- for (var k in c) { if (c[k] !== undefined && c[k] !== '') flat[k] = c[k]; }
+ 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 && c[k] !== '') sub[k] = c[k]; }
+ for (var k in c) { if (c[k] !== undefined) sub[k] = c[k]; }
return sub;
});
return cond;
@@ -141,21 +141,14 @@
// ---- Action field definitions ----
var ACTION_FIELDS = [
- { key: 'give_item', label: 'Give Item', type: 'text' },
- { key: 'take_item', label: 'Take Item', type: 'text' },
- { key: 'teleport', label: 'Teleport Room', type: 'number' },
+ { 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' },
- { key: 'cost', label: 'Cost (Credits)', type: 'number' },
- { key: 'reputation_cost', label: 'Rep Cost', type: 'number' },
];
- var ACTION_BOOLS = [
- { key: 'assign_task', label: 'Assign Task' },
- { key: 'skip_task', label: 'Skip Task' },
- { key: 'extend_task', label: 'Extend Task' },
- { key: 'sawmill', label: 'Sawmill' },
- { key: 'aps_node', label: 'APS Node' },
- ];
+ var ACTION_BOOLS = [];
// ---- Event delegation ----
@@ -173,6 +166,7 @@
else if (type === 'number') { var n = parseFloat(el.value); val = isNaN(n) ? 0 : n; }
else { val = el.value; }
talkSyncPath(path, val);
+ if (path.endsWith('.goto')) renderTalkTree(window._talkData);
} else if (tag === 'SELECT') {
talkSyncPath(path, el.value);
}
@@ -207,11 +201,11 @@
case 'add-node':
addNode();
break;
- case 'add-sequence':
- addSequenceLine(nodeKey);
+ case 'add-message':
+ addMessageLine(nodeKey);
break;
- case 'delete-sequence':
- deleteSequenceLine(nodeKey, parseInt(optIdx, 10));
+ case 'delete-message':
+ deleteMessageLine(nodeKey, parseInt(optIdx, 10));
break;
case 'add-condition':
toggleCondition(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null);
@@ -240,12 +234,6 @@
case 'remove-kv':
removeKVPair(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null, el.getAttribute('data-kvk'), subKey);
break;
- case 'add-shop-item':
- addShopItem(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null);
- break;
- case 'remove-shop-item':
- removeShopItem(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null, parseInt(subIdx, 10));
- break;
}
}
@@ -255,6 +243,8 @@
var container = document.getElementById('talktree');
if (!container) return;
window._talkData = data;
+ window._talkOpenNodes = window._talkOpenNodes || new Set();
+ window._talkOpenNodes.add('start');
if (!data || !data.talk || !data.talk.nodes) {
container.innerHTML = '<p style="color:#888">No conversation tree defined.</p>';
return;
@@ -285,6 +275,9 @@
html += renderAddNodeRow();
html += '</div>';
container.innerHTML = html;
+ if (typeof ced_setupSearchFields === 'function') {
+ setTimeout(function() { ced_setupSearchFields(); }, 10);
+ }
};
// ---- Add/Delete Node ----
@@ -302,7 +295,7 @@
var key = input.value.trim();
if (!key) return;
if (talkNodes()[key]) { notify('Node "' + key + '" already exists.', 'error'); return; }
- talkNodes()[key] = { message: '', options: [] };
+ talkNodes()[key] = { messages: [], options: [] };
input.value = '';
renderTalkTree(window._talkData);
talkSync();
@@ -328,7 +321,7 @@
function renderOrphanNode(key, node, nodes) {
var h = '<div class="talk-node">';
- h += '<div class="talk-node-header" onclick="if(!event.target.closest(\'[data-action]\'))this.parentElement.parentElement.classList.toggle(\'open\')">';
+ h += '<div class="talk-node-header" onclick="if(!event.target.closest(\'[data-action]\'))talkToggleNode(\'' + escAttr(key) + '\',this.parentElement.parentElement)">';
h += '<span class="talk-node-arrow"></span>';
h += '<span class="talk-node-key">' + esc(key) + '</span>';
h += '<span class="talk-node-preview">' + esc((getNodePreview(node) || '').substring(0, 60)) + '</span>';
@@ -349,16 +342,17 @@
var node = nodes[rootKey];
if (!node) return '';
if (seen[rootKey]) {
- return '<div class="talk-node ref"><span class="talk-node-key">' + esc(rootKey) + '</span> <span class="talk-node-preview">(already shown)</span></div>';
+ return '<div class="talk-node ref"><span class="talk-node-key">' + esc(rootKey) + '</span> <a href="javascript:void(0)" onclick="document.getElementById(\'node-' + escAttr(rootKey) + '\').scrollIntoView({behavior:\'smooth\',block:\'center\'});return false" style="color:inherit;text-decoration:underline">(see above)</a></div>';
}
seen[rootKey] = true;
var previewText = getNodePreview(node);
+ var isOpen = window._talkOpenNodes.has(rootKey);
- var html = '<div class="talk-node' + (rootKey === 'start' ? ' open' : '') + '">';
+ 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]\'))this.parentElement.classList.toggle(\'open\')">';
+ 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>';
html += '<span class="talk-node-preview">' + esc((previewText || '').substring(0, 60)) + '</span>';
@@ -390,16 +384,19 @@
}
function getNodePreview(node) {
- if (node.message) return typeof node.message === 'string' ? node.message : (Array.isArray(node.message) ? node.message[0] : '');
- if (node.sequence && node.sequence.length) return node.sequence[0];
+ if (node.messages && node.messages.length) return node.messages[0];
return '';
}
// ---- Node body: condition-gated content ----
+ // 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';
+ var hasCond = ec.mode !== 'none' || (node.condition && typeof node.condition === 'object' && !Array.isArray(node.condition));
+ var hasOpts = node.options && node.options.length > 0;
var html = '';
@@ -412,23 +409,38 @@
html += '<div class="talk-cond-card-body">';
html += renderConditionClauses(ec, nodeKey, null);
html += '<div class="talk-cond-card-sep"></div>';
- html += renderMessageSection(nodeKey, node);
- html += renderSequenceSection(nodeKey, node);
- html += renderActionSection(nodeKey, null, node.action);
+ html += renderMessagesSection(nodeKey, node);
html += renderOptionsSection(nodeKey, node);
+ html += '<div class="talk-cond-card-sep"></div>';
+
+ 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>';
+ }
+
+ 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
} else {
html += '<button class="talk-add-cond-btn" data-action="add-condition" data-nk="' + escAttr(nodeKey) + '">+ Add Condition</button>';
- html += renderMessageSection(nodeKey, node);
- html += renderSequenceSection(nodeKey, node);
- html += renderActionSection(nodeKey, null, node.action);
+ html += renderMessagesSection(nodeKey, node);
html += renderOptionsSection(nodeKey, node);
- }
-
- // Goto fallback — only when no options
- if (!node.options || node.options.length === 0) {
- html += renderGotoFallback(nodeKey, node);
+ html += '<div class="talk-cond-card-sep"></div>';
+ html += renderActionSection(nodeKey, null, node.action);
+ if (!hasOpts) {
+ html += renderGotoFallback(nodeKey, node, 'Auto-advance to');
+ }
}
return html;
@@ -436,39 +448,31 @@
// ---- Goto fallback ----
- function renderGotoFallback(nodeKey, node) {
+ function renderGotoFallback(nodeKey, node, label) {
+ label = label || 'Auto-advance to';
return '<div class="talk-goto-fallback">' +
- '<span class="talk-prop-label">Auto-advance to</span>' +
- '<input class="talk-input talk-input-sm" data-tp="nodes.' + escAttr(nodeKey) + '.goto" list="talkNodeKeys" value="' + escAttr(node.goto || '') + '" placeholder="Fallback node when no options are available...">' +
+ '<span class="talk-prop-label">' + esc(label) + '</span>' +
+ '<input class="talk-input talk-input-sm" data-tp="nodes.' + escAttr(nodeKey) + '.goto" list="talkNodeKeys" value="' + escAttr(node.goto || '') + '" placeholder="Fallback node...">' +
'</div>';
}
- // ---- Message / Sequence ----
-
- function renderMessageSection(nodeKey, node) {
- var msg = node.message || '';
- if (Array.isArray(msg)) msg = msg.join('\n');
- return '<div class="talk-section">' +
- '<div class="talk-section-header"><span class="talk-prop-label">Message</span></div>' +
- '<textarea class="talk-textarea" data-tp="nodes.' + escAttr(nodeKey) + '.message" rows="2" placeholder="NPC dialogue text...">' + esc(msg) + '</textarea>' +
- '</div>';
- }
+ // ---- Messages section ----
- function renderSequenceSection(nodeKey, node) {
- var seq = node.sequence || [];
+ function renderMessagesSection(nodeKey, node) {
+ var msgs = node.messages || [];
var html = '<div class="talk-section">';
- html += '<div class="talk-section-header"><span class="talk-prop-label">Sequence</span></div>';
- seq.forEach(function(line, i) {
+ html += '<div class="talk-section-header"><span class="talk-prop-label">Messages</span></div>';
+ msgs.forEach(function(line, i) {
html += '<div class="talk-seq-line">' +
- '<span class="talk-seq-num">' + (i + 1) + '</span>' +
- '<input class="talk-input talk-input-sm" data-tp="nodes.' + escAttr(nodeKey) + '.sequence.' + i + '" value="' + escAttr(line) + '" placeholder="Sequence line...">' +
- '<button class="talk-seq-del" data-action="delete-sequence" data-nk="' + escAttr(nodeKey) + '" data-oi="' + i + '">x</button>' +
+ '<span class="talk-opt-arrow">&#10132;</span>' +
+ '<input class="talk-input" data-tp="nodes.' + escAttr(nodeKey) + '.messages.' + i + '" value="' + escAttr(line) + '" placeholder="Message line...">' +
+ '<button class="talk-seq-del" data-action="delete-message" data-nk="' + escAttr(nodeKey) + '" data-oi="' + i + '">x</button>' +
'</div>';
});
html += '<div class="talk-seq-add-row">' +
- '<span class="talk-seq-num">' + (seq.length + 1) + '</span>' +
- '<input class="talk-input talk-input-sm" id="talkSeqAdd_' + escAttr(nodeKey) + '" placeholder="Add sequence line...">' +
- '<button class="talk-add-btn" data-action="add-sequence" data-nk="' + escAttr(nodeKey) + '">Add</button>' +
+ '<span class="talk-opt-arrow">&#10132;</span>' +
+ '<input class="talk-input" id="talkMsgAdd_' + escAttr(nodeKey) + '" placeholder="Add message line...">' +
+ '<button class="talk-add-btn" data-action="add-message" data-nk="' + escAttr(nodeKey) + '">Add</button>' +
'</div>';
html += '</div>';
return html;
@@ -518,15 +522,16 @@
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' : '') + '> Not</label>';
+ 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</label>';
+ 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">';
@@ -620,6 +625,10 @@
delete target.condition;
} else {
target.condition = {};
+ if (optIdx !== null) {
+ window._talkAutoOpenDetails = new Set();
+ window._talkAutoOpenDetails.add('cond_' + optIdx + '_' + escAttr(nodeKey));
+ }
}
renderTalkTree(window._talkData);
talkSync();
@@ -661,10 +670,21 @@
ACTION_FIELDS.forEach(function(f) {
var fpath = prefix + '.' + f.key;
var val = (action && action[f.key] !== undefined) ? action[f.key] : '';
- html += '<div class="talk-field-row">' +
- '<span class="talk-prop-label">' + esc(f.label) + '</span>' +
- '<input class="talk-input talk-input-sm" data-tp="' + escAttr(fpath) + '" type="' + f.type + '" value="' + escAttr(String(val)) + '">' +
- '</div>';
+ 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>';
@@ -678,7 +698,6 @@
html += renderKVEditor(action, prefix, 'set_flags', 'Set Flags', nodeKey, optIdx);
html += renderKVEditor(action, prefix, 'set_player_flags', 'Set Player Flags', nodeKey, optIdx);
- html += renderShopEditor(action, prefix, nodeKey, optIdx);
html += '</div>'; // .talk-action-block
return html;
@@ -697,14 +716,14 @@
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">' +
- '<span style="font-size:9px;color:#888;white-space:nowrap;min-width:60px">key: ' + esc(k) + '</span>' +
'<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:100px" data-nk="' + nkAttr + '"' + oiAttr + ' data-kvk="' + escAttr(key) + '">' +
- '<input class="talk-input" placeholder="Value" style="display:none">' +
+ '<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>';
@@ -712,62 +731,30 @@
return html;
}
- function renderShopEditor(action, prefix, nodeKey, optIdx) {
- var shop = action && action.shop;
- var hasShop = shop && typeof shop === 'object' && !Array.isArray(shop);
- var html = '<div class="talk-shop-block">';
- html += '<div class="talk-shop-header">Shop</div>';
-
- if (hasShop) {
- html += '<div class="talk-field-row" style="margin-bottom:4px">' +
- '<span class="talk-prop-label">Shop Message</span>' +
- '<textarea class="talk-textarea" data-tp="' + escAttr(prefix + '.shop.message') + '" rows="2" style="min-height:24px">' + esc(shop.message || '') + '</textarea>' +
- '</div>';
-
- var items = shop.items || [];
- html += '<div class="talk-prop-label" style="margin-bottom:2px">Items</div>';
- items.forEach(function(item, si) {
- var ip = prefix + '.shop.items.' + si;
- html += '<div class="talk-shop-item">' +
- '<input class="talk-input" data-tp="' + escAttr(ip + '.item_id') + '" value="' + escAttr(item.item_id || '') + '" placeholder="Item ID">' +
- '<input class="talk-input-num" data-tp="' + escAttr(ip + '.buy_price') + '" type="number" value="' + (item.buy_price || 0) + '" placeholder="Buy" title="Buy price">' +
- '<input class="talk-input-num" data-tp="' + escAttr(ip + '.sell_price') + '" type="number" value="' + (item.sell_price || 0) + '" placeholder="Sell" title="Sell price">' +
- '<button class="talk-kv-del" data-action="remove-shop-item" data-nk="' + escAttr(nodeKey) + '"' + (optIdx !== null ? ' data-oi="' + optIdx + '"' : '') + ' data-si="' + si + '">x</button>' +
- '</div>';
- });
- html += '<button class="talk-kv-add" data-action="add-shop-item" data-nk="' + escAttr(nodeKey) + '"' + (optIdx !== null ? ' data-oi="' + optIdx + '"' : '') + '>+ Add Item</button>';
- } else {
- html += '<button class="talk-kv-add" data-action="add-shop-item" data-nk="' + escAttr(nodeKey) + '"' + (optIdx !== null ? ' data-oi="' + optIdx + '"' : '') + '>+ Add Shop</button>';
- }
-
- html += '</div>';
- return html;
- }
-
// ---- Options section ----
function renderOptionsSection(nodeKey, node) {
var options = node.options || [];
var html = '<div class="talk-options">';
- html += '<b class="talk-options-label">Options</b>';
+ html += '<span class="talk-options-label">Options</span>';
if (options.length > 0) {
options.forEach(function(opt, idx) {
html += '<div class="talk-opt-edit-row">';
- html += '<span class="talk-opt-arrow">&#10132;</span>';
- html += '<input class="talk-input" data-tp="nodes.' + escAttr(nodeKey) + '.options.' + idx + '.text" value="' + escAttr(opt.text || '') + '" placeholder="Option text...">';
- html += '<input class="talk-input" data-tp="nodes.' + escAttr(nodeKey) + '.options.' + idx + '.goto" list="talkNodeKeys" value="' + escAttr(opt.goto || '') + '" placeholder="Goto..." style="max-width:120px">';
+ html += '<span class="talk-seq-num">' + (idx + 1) + '</span>';
+ html += '<input class="talk-input talk-opt-text" data-tp="nodes.' + escAttr(nodeKey) + '.options.' + idx + '.text" value="' + escAttr(opt.text || '') + '" placeholder="Option text...">';
+ html += '<input class="talk-input talk-opt-goto" data-tp="nodes.' + escAttr(nodeKey) + '.options.' + idx + '.goto" list="talkNodeKeys" value="' + escAttr(opt.goto || '') + '" placeholder="Goto...">';
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>';
} 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">c</button>';
+ 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>';
}
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>';
} 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">a</button>';
+ 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 += '</div>';
@@ -775,7 +762,9 @@
html += '</div>';
if (opt.condition && typeof opt.condition === 'object' && !Array.isArray(opt.condition)) {
- html += '<div class="talk-opt-detail" id="optDetail_cond_' + idx + '_' + escAttr(nodeKey) + '" style="display:none;margin-left:18px">';
+ 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-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>';
@@ -786,12 +775,18 @@
html += '</div>';
}
if (opt.action) {
- html += '<div class="talk-opt-detail" id="optDetail_act_' + idx + '_' + escAttr(nodeKey) + '" style="display:none;margin-left:18px">';
- html += '<div class="talk-cond-card-header" style="border:1px solid var(--border);border-radius:5px 5px 0 0">' +
+ 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-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>';
+ html += '<div class="talk-cond-card-body">';
html += renderActionForm(opt.action, 'nodes.' + escAttr(nodeKey) + '.options.' + idx + '.action', nodeKey, idx);
html += '</div>';
+ html += '</div>';
+ html += '</div>';
}
});
} else {
@@ -799,10 +794,10 @@
}
html += '<div class="talk-add-opt-row">' +
- '<span class="talk-opt-arrow" style="color:#555">&#10132;</span>' +
- '<input class="talk-input" id="talkOptText_' + escAttr(nodeKey) + '" placeholder="Option text...">' +
- '<input class="talk-input" id="talkOptGoto_' + escAttr(nodeKey) + '" list="talkNodeKeys" placeholder="Goto..." style="max-width:120px">' +
- '<span style="width:62px;flex-shrink:0"></span>' +
+ '<span class="talk-seq-num" style="color:#555">' + (options.length + 1) + '</span>' +
+ '<input class="talk-input talk-opt-text" id="talkOptText_' + escAttr(nodeKey) + '" placeholder="Option text...">' +
+ '<input class="talk-input talk-opt-goto" id="talkOptGoto_' + escAttr(nodeKey) + '" list="talkNodeKeys" placeholder="Goto...">' +
+ '<span class="talk-opt-gap"></span>' +
'<button class="talk-add-btn" data-action="add-option" data-nk="' + escAttr(nodeKey) + '">Add</button>' +
'</div>';
@@ -816,6 +811,15 @@
if (el) el.style.display = el.style.display === 'none' ? 'block' : 'none';
};
+ window.talkToggleNode = function(nodeKey, nodeEl) {
+ var open = nodeEl.classList.toggle('open');
+ if (open) {
+ window._talkOpenNodes.add(nodeKey);
+ } else {
+ window._talkOpenNodes.delete(nodeKey);
+ }
+ };
+
// ---- Add/Delete Option ----
function addOption(nodeKey) {
@@ -843,22 +847,22 @@
// ---- Add/Delete Sequence Line ----
- function addSequenceLine(nodeKey) {
- var input = document.getElementById('talkSeqAdd_' + nodeKey);
+ function addMessageLine(nodeKey) {
+ var input = document.getElementById('talkMsgAdd_' + nodeKey);
var text = input ? input.value.trim() : '';
var node = talkNodes()[nodeKey];
if (!node) return;
- if (!node.sequence) node.sequence = [];
- node.sequence.push(text);
+ if (!node.messages) node.messages = [];
+ node.messages.push(text);
renderTalkTree(window._talkData);
talkSync();
}
- function deleteSequenceLine(nodeKey, idx) {
+ function deleteMessageLine(nodeKey, idx) {
var node = talkNodes()[nodeKey];
- if (!node || !node.sequence) return;
- node.sequence.splice(idx, 1);
- if (node.sequence.length === 0) delete node.sequence;
+ if (!node || !node.messages) return;
+ node.messages.splice(idx, 1);
+ if (node.messages.length === 0) delete node.messages;
renderTalkTree(window._talkData);
talkSync();
}
@@ -869,6 +873,10 @@
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();
}
@@ -899,6 +907,11 @@
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') {
@@ -906,7 +919,7 @@
obj = talkGet(basePath);
}
if (obj[key] !== undefined) { notify('Flag "' + key + '" already exists.', 'error'); return; }
- obj[key] = true;
+ obj[key] = val;
renderTalkTree(window._talkData);
talkSync();
}
@@ -923,31 +936,28 @@
talkSync();
}
- // ---- Add/Remove Shop Items ----
-
- function addShopItem(nodeKey, optIdx) {
- var actionPath = optIdx !== null ? 'nodes.' + nodeKey + '.options.' + optIdx + '.action' : 'nodes.' + nodeKey + '.action';
- var action = talkGet(actionPath);
- if (!action || typeof action !== 'object') {
- action = {};
- talkSet(actionPath, action);
+ 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;
}
- if (!action.shop) { action.shop = { items: [] }; }
- if (!action.shop.items) action.shop.items = [];
- action.shop.items.push({ item_id: '', buy_price: 0, sell_price: 0 });
- renderTalkTree(window._talkData);
- talkSync();
- }
-
- function removeShopItem(nodeKey, optIdx, idx) {
- var actionPath = optIdx !== null ? 'nodes.' + nodeKey + '.options.' + optIdx + '.action' : 'nodes.' + nodeKey + '.action';
- var action = talkGet(actionPath);
- if (!action || !action.shop || !action.shop.items) return;
- action.shop.items.splice(idx, 1);
- if (action.shop.items.length === 0) delete action.shop;
+ obj[newKey] = obj[oldKey];
+ delete obj[oldKey];
renderTalkTree(window._talkData);
talkSync();
- }
+ };
// ---- Global event listeners ----
@@ -988,10 +998,10 @@
return;
}
- if (el.id && el.id.startsWith('talkSeqAdd_')) {
+ if (el.id && el.id.startsWith('talkMsgAdd_')) {
e.preventDefault();
- var nk = el.id.replace('talkSeqAdd_', '');
- addSequenceLine(nk);
+ var nk = el.id.replace('talkMsgAdd_', '');
+ addMessageLine(nk);
return;
}