aboutsummaryrefslogtreecommitdiff
path: root/internal/admin
diff options
context:
space:
mode:
Diffstat (limited to 'internal/admin')
-rw-r--r--internal/admin/static/admin.css2
-rw-r--r--internal/admin/static/intereditor.js118
-rw-r--r--internal/admin/static/itemeditor.js74
-rw-r--r--internal/admin/static/mobeditor.js2
-rw-r--r--internal/admin/static/objecteditor.js4
-rw-r--r--internal/admin/static/triggerseditor.js37
-rw-r--r--internal/admin/templates/items.html1
7 files changed, 218 insertions, 20 deletions
diff --git a/internal/admin/static/admin.css b/internal/admin/static/admin.css
index f77e55a..496a031 100644
--- a/internal/admin/static/admin.css
+++ b/internal/admin/static/admin.css
@@ -391,6 +391,8 @@ g.ud-hover:hover text{font-weight:bold}
.ie-act-rm{flex-shrink:0}
.ie-act-kv{background:var(--bg);border:1px solid var(--border);border-radius:3px;padding:6px}
.ie-act-kv-header{display:flex;align-items:center;gap:8px;margin-bottom:4px}
+.ie-drop-table{display:flex;flex-direction:column;gap:4px}
+.ie-drop-row{display:flex;gap:6px;align-items:center}
.ie-kv-list{display:flex;flex-direction:column;gap:3px}
.ie-kv-row{display:flex;gap:4px;align-items:center}
.ie-kv-key,.ie-kv-val{font-size:11px;padding:2px 4px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:3px;font-family:monospace;width:120px}
diff --git a/internal/admin/static/intereditor.js b/internal/admin/static/intereditor.js
index c4f16e7..0e2c03d 100644
--- a/internal/admin/static/intereditor.js
+++ b/internal/admin/static/intereditor.js
@@ -3,7 +3,8 @@
// Trigger = { lock, item_id?, condition?, steps[], on_player_flag?, on_global_flag?, value? }
// Step = { condition?, wait, messages[], broadcast, broadcast_global,
// spawn_mob, despawn_mob, set_global_flags, set_player_flags,
-// give_item, take_item, teleport, heal, credits, aps_node }
+// give_item, take_item, teleport, heal, credits, aps_node,
+// drop_table[] }
// Shared by objecteditor.js, mobeditor.js, and map.js.
// ── Condition leaf types ──
@@ -32,6 +33,7 @@ var IE_ACT_TYPES = [
{key: 'set_player_flags', label: 'Set Player Flags', kind: 'kv'},
{key: 'give_item', label: 'Give Item', kind: 'search', entity: 'items'},
{key: 'take_item', label: 'Take Item', kind: 'search', entity: 'items'},
+ {key: 'drop_table', label: 'Drop Table', kind: 'droptable'},
{key: 'teleport', label: 'Teleport', kind: 'number', hint: 'room ID'},
{key: 'heal', label: 'Heal', kind: 'number', hint: 'HP to restore'},
{key: 'credits', label: 'Credits', kind: 'number', hint: 'amount (negative = cost)'},
@@ -83,6 +85,62 @@ function ie_renderBottomAddbar(secId, secPath, idx) {
return h;
}
+// ── Drop Table block renderer (shared between intereditor and triggerseditor) ──
+
+function ie_renderDropTableBlock(drops, onAddItem, onAddTable, onRemove) {
+ drops = drops || [];
+ var h = '<div class="ie-drop-table">';
+ drops.forEach(function(row, ri) {
+ var kind = row.table ? 'table' : (row.item_id ? 'item' : (row._kind || 'item'));
+ h += '<div class="ie-drop-row">';
+ if (kind === 'table') {
+ h += '<div class="obj-search" style="position:relative;min-width:0;flex:2"><input class="search-input ie-drop-table-input" data-search-type="drops" value="' + escAttr(row.table || '') + '" placeholder="Table ID"><div class="search-results" style="display:none"></div></div>';
+ } else {
+ h += '<div class="obj-search" style="position:relative;min-width:0;flex:2"><input class="search-input ie-drop-item-input" data-search-type="items" value="' + escAttr(row.item_id || '') + '" placeholder="Item ID"><div class="search-results" style="display:none"></div></div>';
+ }
+ h += '<input class="ie-drop-weight" type="number" value="' + (row.weight || 10) + '" placeholder="Weight" style="width:60px" min="0">';
+ h += '<input class="ie-drop-qty" type="number" value="' + (row.quantity || 1) + '" placeholder="Qty" style="width:50px" min="0">';
+ h += '<button class="btn btn-sm btn-danger" onclick="' + onRemove.replace(/__RI__/g, String(ri)) + '">X</button>';
+ h += '</div>';
+ });
+ h += '<div style="display:flex;gap:6px;margin-top:4px">';
+ h += '<button class="btn btn-sm obj-sub-add" onclick="' + onAddItem + '">+ Add Item</button>';
+ h += '<button class="btn btn-sm obj-sub-add" onclick="' + onAddTable + '">+ Add Table</button>';
+ h += '</div>';
+ h += '</div>';
+ return h;
+}
+
+function ie_collectDropTableBlock(container) {
+ if (!container) return null;
+ var rows = container.querySelectorAll(':scope > .ie-drop-row');
+ var out = [];
+ rows.forEach(function(row) {
+ var tableInput = row.querySelector('.ie-drop-table-input');
+ var itemInput = row.querySelector('.ie-drop-item-input');
+ var weightInput = row.querySelector('.ie-drop-weight');
+ var qtyInput = row.querySelector('.ie-drop-qty');
+ var entry = {};
+ if (tableInput && tableInput.value.trim()) {
+ entry.table = tableInput.value.trim();
+ entry._kind = 'table';
+ } else if (itemInput && itemInput.value.trim()) {
+ entry.item_id = itemInput.value.trim();
+ entry._kind = 'item';
+ } else if (itemInput) {
+ entry.item_id = '';
+ entry._kind = 'item';
+ } else {
+ entry.table = '';
+ entry._kind = 'table';
+ }
+ entry.weight = weightInput ? (parseInt(weightInput.value, 10) || 0) : 0;
+ entry.quantity = qtyInput ? (parseInt(qtyInput.value, 10) || 0) : 0;
+ out.push(entry);
+ });
+ return out.length > 0 ? out : null;
+}
+
// ie_addStepKind creates a new step initialised with a single action kind.
function ie_addStepKind(secId, secPath, idx, kind) {
var ed = window._editorData;
@@ -103,6 +161,7 @@ function ie_addStepKind(secId, secPath, idx, kind) {
if (at.kind === 'kv') step[kind] = {};
else if (at.kind === 'checkbox') step[kind] = true;
else if (at.kind === 'number') step[kind] = 0;
+ else if (at.kind === 'droptable') step[kind] = [];
else step[kind] = '';
}
ed[secPath][idx].steps.push(step);
@@ -507,6 +566,11 @@ function ie_renderAct(stepObj, secId, idx, si, secPath) {
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 += '</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>';
} 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">';
@@ -595,6 +659,11 @@ function ie_collectAct(container) {
var key = kvEl.getAttribute('data-ie-act-key');
if (!key) return;
if (key === 'messages') return;
+ if (key === 'drop_table') {
+ var dt = ie_collectDropTableBlock(kvEl.querySelector('.ie-drop-table'));
+ if (dt) r.drop_table = dt;
+ return;
+ }
var map = {};
kvEl.querySelectorAll('.ie-kv-row').forEach(function(row) {
var kEl = row.querySelector('.ie-kv-key');
@@ -1013,6 +1082,8 @@ function ie_addActEffect(secId, secPath, idx, si, effectKey) {
step[effectKey] = true;
} else if (at.kind === 'number') {
step[effectKey] = 0;
+ } else if (at.kind === 'droptable') {
+ step[effectKey] = [];
} else {
step[effectKey] = '';
}
@@ -1077,8 +1148,37 @@ function ie_addMessages(secId, secPath, idx, si) {
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;
- ed[secPath][idx].steps[si].messages = [''];
- ced_syncDOMToData(ed);
+ var step = ed[secPath][idx].steps[si];
+ if (!step.messages || !Array.isArray(step.messages)) step.messages = [];
+ step.messages.push('');
+ window._ieRenderCurrent();
+}
+
+// ── 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];
+ 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();
+}
+
+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];
+ if (!Array.isArray(step.drop_table)) return;
+ step.drop_table.splice(ri, 1);
window._ieRenderOnly();
}
@@ -1214,6 +1314,18 @@ function ie_cleanStep(step) {
if (!step || !Object.prototype.hasOwnProperty.call(step, at.key)) return;
var v = step[at.key];
if (v === undefined || v === null || v === '') return;
+ if (at.key === 'drop_table' && Array.isArray(v)) {
+ var cleaned = v.map(function(e) {
+ var c = {};
+ if (e.item_id) c.item_id = e.item_id;
+ if (e.table) c.table = e.table;
+ if (e.weight) c.weight = e.weight;
+ if (e.quantity) c.quantity = e.quantity;
+ return c;
+ }).filter(function(e) { return Object.keys(e).length > 0; });
+ if (cleaned.length > 0) s.drop_table = cleaned;
+ return;
+ }
if (typeof v === 'object') {
if (Object.keys(v).length === 0) return;
s[at.key] = v;
diff --git a/internal/admin/static/itemeditor.js b/internal/admin/static/itemeditor.js
index a86d240..53242ab 100644
--- a/internal/admin/static/itemeditor.js
+++ b/internal/admin/static/itemeditor.js
@@ -84,13 +84,9 @@ var SECTIONS = [
]
},
{
- id: 'search', label: 'Search',
- detect: function(d) { return d.search_table || d.search_ticks; },
- fields: [
- ['search_table', 'Search Table', 'search', 'e.g. gem_table', '', null, 'drops'],
- ['search_ticks', 'Search Ticks', 'number', '8', 'narrow'],
- ['search_message', 'Search Message', 'text', 'You search...', 'wide'],
- ]
+ id: 'search', label: 'Search', path: 'search', isArray: true, fullWidth: true, interactionStyle: true, itemIDAllowed: false,
+ detect: function(d) { return d.search && Array.isArray(d.search); },
+ fields: []
},
{
id: 'food', label: 'Food',
@@ -126,6 +122,9 @@ var SECTIONS = [
];
function initItemEditor() {
+ window._ieRenderCurrent = renderCurrent;
+ window._ieRenderOnly = function() { if (itemID && itemData) renderItemEditor(itemID, itemData); };
+ window._ieSyncAll = function(ed) { if (ed) ie_syncAllInteractions(ed, SECTIONS); };
editorType = 'items';
editorFields = [];
loadList();
@@ -167,6 +166,7 @@ function loadItemEditor(id) {
API.get('/api/items/' + encodeURIComponent(id)).then(function(data) {
itemData = data;
window._editorData = itemData;
+ window._interVis = {};
renderItemEditor(id, data);
}).catch(function(e) {
$('#editorMain').innerHTML = '<p style="color:var(--danger)">Failed to load: ' + esc(e.message) + '</p>';
@@ -225,6 +225,7 @@ function renderItemEditor(id, data) {
function renderCurrent() {
ced_syncDOMToData(itemData);
+ if (window._ieSyncAll) window._ieSyncAll(itemData);
if (!itemData || !itemID) return;
renderItemEditor(itemID, itemData);
}
@@ -250,7 +251,22 @@ function renderCard(sec, data) {
h += '<div class="obj-card-body">';
if (sec.isArray) {
- h += renderArraySection(sec, data);
+ if (sec.interactionStyle) {
+ window._interVis = window._interVis || {};
+ if (!window._interVis[sec.id]) window._interVis[sec.id] = {};
+ h += ie_renderArraySection({
+ sec: sec,
+ data: data,
+ visMap: window._interVis[sec.id],
+ showRemove: function(idx) { return 'removeArrayItem(\'' + sec.id + '\',' + idx + ')'; },
+ showField: function(secId, idx, key) { return 'showInterField(\'' + secId + '\',' + idx + ',\'' + key + '\')'; },
+ hideField: function(secId, idx, key) { return 'hideInterField(\'' + secId + '\',' + idx + ',\'' + key + '\')'; },
+ renderField: function(sec, f, data, idx, optStyle) { return renderField(sec, f, data, idx, null, optStyle); },
+ onAdd: function() { return 'addArrayItem(\'' + sec.id + '\')'; },
+ });
+ } else {
+ h += renderArraySection(sec, data);
+ }
} else if (sec.id === 'core') {
h += renderCoreCard(data);
} else if (sec.id === 'equipment') {
@@ -964,6 +980,8 @@ function addSection() {
if (sec.path) {
if (sec.id === 'equipment') {
itemData.equipment = {type:'', slot:'', attack_type:'', speed:0, junk:''};
+ } else if (sec.isArray && sec.interactionStyle) {
+ itemData[sec.path] = [];
} else {
itemData[sec.path] = {};
}
@@ -981,11 +999,15 @@ function addArrayItem(cardID) {
var sec = SECTIONS.find(function(s) { return s.id === cardID; });
if (!sec || !sec.isArray) return;
var arr = itemData[sec.path] || [];
- var empty = {};
- sec.fields.forEach(function(f) {
- empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : '');
- });
- arr.push(empty);
+ if (sec.interactionStyle) {
+ arr.push({steps: []});
+ } else {
+ var empty = {};
+ sec.fields.forEach(function(f) {
+ empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : '');
+ });
+ arr.push(empty);
+ }
itemData[sec.path] = arr;
renderCurrent();
}
@@ -994,11 +1016,24 @@ function removeArrayItem(cardID, idx) {
var sec = SECTIONS.find(function(s) { return s.id === cardID; });
if (!sec || !sec.isArray) return;
ced_syncDOMToData(itemData);
+ if (window._ieSyncAll) window._ieSyncAll(itemData);
var arr = itemData[sec.path] || [];
arr.splice(idx, 1);
renderItemEditor(itemID, itemData);
}
+function showInterField(secId, idx, key) {
+ window._interVis = window._interVis || {};
+ ie_showField(window._interVis, secId, idx, key);
+ renderCurrent();
+}
+
+function hideInterField(secId, idx, key) {
+ window._interVis = window._interVis || {};
+ ie_hideField(window._interVis, secId, idx, key);
+ renderCurrent();
+}
+
// =========================================================================
// Subtable row add/remove
// =========================================================================
@@ -1332,6 +1367,19 @@ function saveItem() {
var el = document.getElementById(fid);
if (el) item[f[0]] = collectFieldValue(el, f[2]);
});
+ if (sec.interactionStyle) {
+ var dataArr = itemData[sec.path] || [];
+ var dataItem = dataArr[idx] || {};
+ if (dataItem.condition && typeof dataItem.condition === 'object') {
+ var cleanedCond = ie_cleanCondition(dataItem.condition);
+ if (cleanedCond) item.condition = cleanedCond;
+ }
+ if (dataItem.lock) item.lock = true;
+ if (dataItem.steps && Array.isArray(dataItem.steps)) {
+ var cleanedSteps = dataItem.steps.map(ie_cleanStep).filter(function(s) { return Object.keys(s).length > 0; });
+ if (cleanedSteps.length > 0) item.steps = cleanedSteps;
+ }
+ }
if (Object.keys(item).length > 0) arr.push(item);
});
if (arr.length > 0) out[sec.path] = arr;
diff --git a/internal/admin/static/mobeditor.js b/internal/admin/static/mobeditor.js
index 183aef9..29664dc 100644
--- a/internal/admin/static/mobeditor.js
+++ b/internal/admin/static/mobeditor.js
@@ -121,7 +121,7 @@ var MOB_SECTIONS = [
]
},
{
- id: 'on_kill', label: 'On Kill', labelHint: '(fired when this mob is defeated or its task completes — additive over drops)', path: 'on_kill', isArray: true, fullWidth: true, interactionStyle: true,
+ id: 'on_kill', label: 'Kill', labelHint: '(fired when this mob is defeated or its task completes — additive over drops)', path: 'on_kill', isArray: true, fullWidth: true, interactionStyle: true,
detect: function(d) { return d.on_kill && Array.isArray(d.on_kill); },
fields: [
['item_id', 'Item ID', 'search', '(optional — only fires if you wield this weapon when the mob is defeated)', '', null, 'items'],
diff --git a/internal/admin/static/objecteditor.js b/internal/admin/static/objecteditor.js
index 1abccbf..88bc5b7 100644
--- a/internal/admin/static/objecteditor.js
+++ b/internal/admin/static/objecteditor.js
@@ -86,14 +86,14 @@ var SECTIONS = [
]
},
{
- id: 'on_use', label: 'On Use', labelHint: '(what happens when you use items on this)', path: 'on_use', isArray: true, fullWidth: true, interactionStyle: true,
+ id: 'on_use', label: 'Use', labelHint: '(what happens when you use items on this)', path: 'on_use', isArray: true, fullWidth: true, interactionStyle: true,
detect: function(d) { return d.on_use && Array.isArray(d.on_use); },
fields: [
['item_id', 'Item ID', 'search', 'e.g. keycard (empty = bare use)', '', null, 'items'],
]
},
{
- id: 'on_look', label: 'On Look', labelHint: '(what happens when you "look <this>")', path: 'on_look', isArray: true, fullWidth: true, interactionStyle: true,
+ id: 'on_look', label: 'Look', labelHint: '(what happens when you "look <this>")', path: 'on_look', isArray: true, fullWidth: true, interactionStyle: true,
detect: function(d) { return d.on_look && Array.isArray(d.on_look); },
fields: [
['item_id', 'Item ID', 'search', '(optional — only fires if you carry this item)', '', null, 'items'],
diff --git a/internal/admin/static/triggerseditor.js b/internal/admin/static/triggerseditor.js
index 29759eb..dbc1e41 100644
--- a/internal/admin/static/triggerseditor.js
+++ b/internal/admin/static/triggerseditor.js
@@ -110,7 +110,7 @@ function syncStepEffects(row, si) {
if (!effectsEl) return;
var step = triggerData.steps[si] || {};
['messages','broadcast','broadcast_global','teleport','heal','credits',
- 'give_item','take_item','despawn_mob','aps_node'].forEach(function(k) { delete step[k]; });
+ 'give_item','take_item','despawn_mob','aps_node','drop_table'].forEach(function(k) { delete step[k]; });
delete step.set_global_flags; delete step.set_player_flags; delete step.spawn_mob;
var msgInputs = effectsEl.querySelectorAll('.trig-msg-input');
@@ -147,6 +147,12 @@ function syncStepEffects(row, si) {
step.spawn_mob.id = smInput.value.trim();
}
+ var dtBlock = effectsEl.querySelector('.ie-drop-table');
+ if (dtBlock) {
+ var dt = ie_collectDropTableBlock(dtBlock);
+ if (dt) step.drop_table = dt;
+ }
+
triggerData.steps[si] = step;
}
@@ -336,9 +342,37 @@ function renderStepEffects(step, si) {
h += renderKVSection('Set Player Flags', 'set_player_flags', step.set_player_flags);
}
+ if (Array.isArray(step.drop_table) || step.drop_table !== undefined) {
+ h += '<div class="ie-act-kv"><div class="ie-act-kv-header"><span>Drop Table</span></div>';
+ h += ie_renderDropTableBlock(step.drop_table, 'trigAddDropRow('+si+',\'item\')', 'trigAddDropRow('+si+',\'table\')', 'trigRemoveDropRow('+si+',__RI__)');
+ h += '</div>';
+ }
+
return h;
}
+function trigAddDropRow(si, kind) {
+ if (!triggerData || !Array.isArray(triggerData.steps) || !triggerData.steps[si]) return;
+ syncAllStepEffects();
+ var step = triggerData.steps[si];
+ 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'});
+ }
+ renderCurrent();
+}
+
+function trigRemoveDropRow(si, ri) {
+ if (!triggerData || !Array.isArray(triggerData.steps) || !triggerData.steps[si]) return;
+ syncAllStepEffects();
+ var step = triggerData.steps[si];
+ if (!Array.isArray(step.drop_table)) return;
+ step.drop_table.splice(ri, 1);
+ renderCurrent();
+}
+
function addStep() {
if (!triggerData) return;
if (!Array.isArray(triggerData.steps)) triggerData.steps = [];
@@ -378,6 +412,7 @@ function addStepEffect(si, kind) {
if (!at) return;
if (at.kind === 'kv') step[kind] = {};
else if (at.kind === 'checkbox') step[kind] = true;
+ else if (at.kind === 'droptable') step[kind] = [];
else if (at.kind === 'number') step[kind] = 0;
else if (at.kind === 'search') step[kind] = '';
else step[kind] = '';
diff --git a/internal/admin/templates/items.html b/internal/admin/templates/items.html
index 5d82ace..7efeded 100644
--- a/internal/admin/templates/items.html
+++ b/internal/admin/templates/items.html
@@ -11,6 +11,7 @@
</div>
<script src="/static/editor.js"></script>
<script src="/static/cardeditor.js"></script>
+<script src="/static/intereditor.js"></script>
<script src="/static/itemeditor.js"></script>
<script>
initItemEditor();