1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
window.API = {
async get(url) { const r = await fetch(url,{credentials:'same-origin'}); if(!r.ok)throw new Error(await r.text()); return r.json(); },
async post(url,data) { const r = await fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data),credentials:'same-origin'}); if(!r.ok)throw new Error(await r.text()); return r.json(); },
async put(url,data) { const r = await fetch(url,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data),credentials:'same-origin'}); if(!r.ok)throw new Error(await r.text()); return r.json(); },
async del(url) { const r = await fetch(url,{method:'DELETE',credentials:'same-origin'}); if(!r.ok)throw new Error(await r.text()); return r.json(); }
};
window.$ = (sel) => document.querySelector(sel);
window.$$ = (sel) => document.querySelectorAll(sel);
window.notify = function(msg, type) {
var el = document.createElement('div');
el.className = 'notification ' + (type || '');
el.textContent = msg;
document.body.appendChild(el);
setTimeout(function() { el.remove(); }, 3000);
};
window.undoState = null;
window.updateUndoBar = function() {
API.get('/api/undo/state').then(function(s) {
undoState = s;
var uBtn = $('.undo-btn');
var rBtn = $('.redo-btn');
var uLabel = $('.undo-label');
if (uBtn) { uBtn.disabled = !s.can_undo; uBtn.title = s.undo_desc || ''; }
if (rBtn) { rBtn.disabled = !s.can_redo; rBtn.title = s.redo_desc || ''; }
if (uLabel) { uLabel.textContent = s.undo_desc || ''; }
}).catch(function() {});
};
window.doUndo = function() {
API.post('/api/undo/undo').then(function(r) { notify(r.message || 'Undo complete', 'success'); updateUndoBar(); if(r.has_duplicates) { window.location = '/'; return; } if(window.refreshPage)refreshPage(); }).catch(function(e) { notify('Undo failed: '+e.message, 'error'); });
};
window.doRedo = function() {
API.post('/api/undo/redo').then(function(r) { notify(r.message || 'Redo complete', 'success'); updateUndoBar(); if(r.has_duplicates) { window.location = '/'; return; } if(window.refreshPage)refreshPage(); }).catch(function(e) { notify('Redo failed: '+e.message, 'error'); });
};
window.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
e.preventDefault();
if (e.shiftKey) { doRedo(); } else { doUndo(); }
}
});
window.saveForm = function(url, data, msg) {
API.put(url, data).then(function() {
updateUndoBar();
notify(msg || 'Saved', 'success');
}).catch(function(e) { notify('Save failed: '+e.message, 'error'); });
};
window.entityRename = function(type, oldID, newID) {
return API.post('/api/' + type + '/rename', {id: oldID, new_id: newID}).then(function(r) {
notify('Renamed ' + oldID + ' to ' + newID, 'success');
updateUndoBar();
return r;
});
};
window.renderRenameableHeader = function(label, id) {
var h = '<div class="rename-header">';
h += '<h2 class="rename-header-text">' + esc(label) + ': ' + esc(id) + '</h2>';
h += '<span class="rename-pencil" onclick="startHeaderRename(this,\'' + escAttr(id) + '\')" title="Rename">\u270E</span>';
h += '<div class="rename-edit-row" style="display:none"></div>';
h += '</div>';
return h;
};
window.handleHeaderRename = function(oldID, newID) {
var type = window.editorType || '';
if (!type) return;
entityRename(type, oldID, newID).then(function() {
window.currentID = newID;
var typeSingular = type.slice(0, -1) + 'ID';
if (window[typeSingular] !== undefined) window[typeSingular] = newID;
if (window.loadList) window.loadList();
if (window.loadItem) window.loadItem(newID);
}).catch(function(e) { notify('Rename failed: ' + e.message, 'error'); });
};
window.startHeaderRename = function(pencilEl, oldID) {
var header = pencilEl.parentElement;
var textSpan = header.querySelector('.rename-header-text');
var editRow = header.querySelector('.rename-edit-row');
textSpan.style.display = 'none';
pencilEl.style.display = 'none';
editRow.style.display = 'flex';
editRow.innerHTML = '<input class="rename-input" value="' + escAttr(oldID) + '" maxlength="128">' +
'<button class="btn btn-sm rename-btn-confirm" title="Confirm">\u2713</button>' +
'<button class="btn btn-sm rename-btn-cancel" title="Cancel">\u2717</button>';
var input = editRow.querySelector('.rename-input');
var confirmBtn = editRow.querySelector('.rename-btn-confirm');
var cancelBtn = editRow.querySelector('.rename-btn-cancel');
input.focus();
input.select();
function cancel() {
textSpan.style.display = '';
pencilEl.style.display = '';
editRow.style.display = 'none';
}
function confirm() {
var newID = input.value.trim();
if (!newID || newID === oldID) { cancel(); return; }
if (window.handleHeaderRename) handleHeaderRename(oldID, newID);
}
cancelBtn.onclick = cancel;
input.onkeydown = function(e) { if (e.key === 'Enter') confirm(); else if (e.key === 'Escape') cancel(); };
confirmBtn.onclick = confirm;
};
document.addEventListener('DOMContentLoaded', function() { updateUndoBar(); });
|