aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/templates/files.html
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-11 18:30:58 -0400
committerhistoria <[not public]>2026-07-11 18:30:58 -0400
commit2bacbb08d1ac67c4f63ee535728cd4e8760e3211 (patch)
treed8ee366b3d32b486eb05c5e10bc65cf194ae4c23 /internal/admin/templates/files.html
parent131cbbe586463a7232f332650af5f0b2c13dc7e9 (diff)
downloadthehouseoficarus-2bacbb08d1ac67c4f63ee535728cd4e8760e3211.tar.gz
feat(admin): unify context menu, add context menu to files tab
Diffstat (limited to 'internal/admin/templates/files.html')
-rw-r--r--internal/admin/templates/files.html130
1 files changed, 119 insertions, 11 deletions
diff --git a/internal/admin/templates/files.html b/internal/admin/templates/files.html
index b89a142..5a5106f 100644
--- a/internal/admin/templates/files.html
+++ b/internal/admin/templates/files.html
@@ -9,12 +9,14 @@
</div>
</div>
<script>
-var currentPath = '';
+var currentDir = '';
+var currentFile = null;
window._allFiles = [];
function loadFiles(path) {
- currentPath = path || '';
- API.get('/api/files' + (path ? '?path=' + encodeURIComponent(path) : '')).then(function(r){
+ currentDir = path || '';
+ currentFile = null;
+ return API.get('/api/files' + (path ? '?path=' + encodeURIComponent(path) : '')).then(function(r){
var files = r.files || r || [];
window._allFiles = files;
renderFileList('');
@@ -26,17 +28,17 @@ function renderFileList(filter){
var f = filter.toLowerCase();
var items = window._allFiles.filter(function(x){ return !f || x.name.toLowerCase().indexOf(f) >= 0; });
var html = '<div class="item" onclick="loadFiles(\'\')" style="color:#6cf">[root]</div>';
- if (currentPath) {
- var parent = currentPath.split('/').slice(0,-1).join('/');
+ if (currentDir) {
+ var parent = currentDir.split('/').slice(0,-1).join('/');
html += '<div class="item" onclick="loadFiles(\''+escJs(parent)+'\')" style="color:#6cf">[..]</div>';
}
items.forEach(function(x){
if (x.dir) {
- var sub = currentPath ? currentPath + '/' + x.name : x.name;
+ var sub = currentDir ? currentDir + '/' + x.name : x.name;
html += '<div class="item" onclick="loadFiles(\''+escJs(sub)+'\')" style="color:#fc6">'+escHtml(x.name)+'/</div>';
} else {
- var fp = currentPath ? currentPath + '/' + x.name : x.name;
- html += '<div class="item" onclick="loadFileContent(\''+escJs(fp)+'\',\''+escJs(x.name)+'\')">'+escHtml(x.name)+'</div>';
+ var fp = currentDir ? currentDir + '/' + x.name : x.name;
+ html += '<div class="item" data-path="'+escHtml(fp)+'" onclick="loadFileContent(\''+escJs(fp)+'\',\''+escJs(x.name)+'\')" oncontextmenu="showFileContextMenu(event,\''+escJs(fp)+'\',\''+escJs(x.name)+'\')">'+escHtml(x.name)+'</div>';
}
});
el.innerHTML = html;
@@ -45,7 +47,7 @@ function renderFileList(filter){
function filterFiles(val) { renderFileList(val); }
function loadFileContent(path, name) {
- currentPath = path;
+ currentFile = path;
API.get('/api/files?path=' + encodeURIComponent(path)).then(function(r){
var content = r.content || r.raw || JSON.stringify(r,null,2);
var html = '<h2>'+escHtml(name)+'</h2>';
@@ -66,13 +68,119 @@ function saveFile(path) {
}).then(function(r) {
if (!r.ok) throw new Error('Save failed');
return r.json();
- }).then(function() {
+ }).then(function(data) {
+ if (data && data.error) throw new Error(data.error);
notify('File saved', 'success');
updateUndoBar();
}).catch(function(e) { notify('Save failed: '+e.message, 'error'); });
}
-window.refreshPage = function() { loadFiles(currentPath); };
+function showFileContextMenu(e, path, name) {
+ openContextMenu(e, [
+ {label: 'Rename', handler: function() { startInlineFileRename(path, name); }},
+ {label: 'Duplicate', handler: function() { duplicateFile(path, name); }},
+ {label: 'Delete', cls: 'danger', handler: function() { deleteFile(path); }},
+ ]);
+}
+
+function deleteFile(path) {
+ API.del('/api/files?path=' + encodeURIComponent(path)).then(function(r) {
+ notify('File deleted', 'success');
+ updateUndoBar();
+ if (currentFile === path) {
+ $('#editorMain').innerHTML = '<p style="color:#888;text-align:center;margin-top:60px">Select a file to edit</p>';
+ currentFile = null;
+ }
+ loadFiles(currentDir);
+ }).catch(function(e) { notify('Delete failed: ' + e.message, 'error'); });
+}
+
+function duplicateFile(path, name) {
+ API.get('/api/files?path=' + encodeURIComponent(path)).then(function(r) {
+ var content = r.content || '';
+ var dot = name.lastIndexOf('.');
+ var base, ext;
+ if (dot > 0) { base = name.substring(0, dot); ext = name.substring(dot); }
+ else { base = name; ext = ''; }
+
+ var baseName = base + '_copy';
+ var newName = baseName + ext;
+ var counter = 2;
+ while (true) {
+ var exists = false;
+ for (var i = 0; i < window._allFiles.length; i++) {
+ if (window._allFiles[i].name === newName) { exists = true; break; }
+ }
+ if (!exists) break;
+ newName = baseName + '_' + counter + ext;
+ counter++;
+ }
+
+ var newPath = currentDir ? currentDir + '/' + newName : newName;
+ fetch('/api/files?path=' + encodeURIComponent(newPath), {
+ method: 'PUT',
+ headers: {'Content-Type': 'text/plain'},
+ body: content,
+ credentials: 'same-origin'
+ }).then(function(r) {
+ if (!r.ok) throw new Error('Duplicate failed');
+ return r.json();
+ }).then(function(data) {
+ if (data && data.error) throw new Error(data.error);
+ notify('Duplicated as ' + newName, 'success');
+ updateUndoBar();
+ loadFiles(currentDir).then(function() { loadFileContent(newPath, newName); });
+ }).catch(function(e) { notify('Duplicate failed: ' + e.message, 'error'); });
+ }).catch(function(e) { notify('Duplicate failed: ' + e.message, 'error'); });
+}
+
+function startInlineFileRename(path, name) {
+ var el = document.querySelector('#listEntries .item[data-path="' + escHtml(path) + '"]');
+ if (!el) return;
+ var origHTML = el.innerHTML;
+ el.innerHTML = '<input class="item-rename-input" value="' + escHtml(name) + '" maxlength="255">';
+ var input = el.querySelector('.item-rename-input');
+ input.focus();
+ input.select();
+
+ function finish(val) {
+ val = (val || '').trim();
+ if (!val || val === name) { el.innerHTML = origHTML; return; }
+ if (val.indexOf('/') >= 0 || val.indexOf('\\') >= 0 || val === '.' || val === '..') {
+ notify('Invalid file name', 'error');
+ el.innerHTML = origHTML;
+ return;
+ }
+ for (var i = 0; i < val.length; i++) {
+ var c = val.charCodeAt(i);
+ if (c < 32 || c === 127) { notify('Invalid file name', 'error'); el.innerHTML = origHTML; return; }
+ }
+ for (var j = 0; j < window._allFiles.length; j++) {
+ if (window._allFiles[j].name === val) {
+ notify('A file named "' + val + '" already exists.', 'error');
+ el.innerHTML = origHTML;
+ return;
+ }
+ }
+ API.post('/api/files/rename', {path: path, new_name: val}).then(function(r) {
+ var newPath = r.path || (currentDir ? currentDir + '/' + val : val);
+ notify('Renamed to ' + val, 'success');
+ updateUndoBar();
+ loadFiles(currentDir).then(function() { loadFileContent(newPath, val); });
+ }).catch(function(e) {
+ notify('Rename failed: ' + e.message, 'error');
+ el.innerHTML = origHTML;
+ });
+ }
+
+ input.onkeydown = function(e) {
+ if (e.key === 'Enter') { input.blur(); }
+ else if (e.key === 'Escape') { el.innerHTML = origHTML; }
+ };
+ input.onblur = function() { finish(input.value); };
+}
+
+window.refreshPage = function() { loadFiles(currentDir); };
function escHtml(s) { return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
function escJs(s) { return String(s||'').replace(/\\/g,'\\\\').replace(/'/g,"\\'").replace(/"/g,'\\"'); }