266 lines
7.8 KiB
HTML
266 lines
7.8 KiB
HTML
<!DOCTYPE HTML>
|
|
<html lang="en">
|
|
<head>
|
|
<title>Edit file</title>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<!--<link href="/css/global-style.css" rel="stylesheet">-->
|
|
<link href="/css/nav.css" rel="stylesheet">
|
|
|
|
<style>
|
|
/* --- Shared theme (matches File Manager) --- */
|
|
:root{
|
|
--primary-color:#007bff;
|
|
--primary-hover:#0056b3;
|
|
--background-color:#f4f7f6;
|
|
--card-background:#ffffff;
|
|
--text-color:#333;
|
|
--border-color:#ddd;
|
|
--border-radius:8px;
|
|
--box-shadow:0 4px 6px rgba(0,0,0,.1);
|
|
}
|
|
|
|
html, body {
|
|
height: 100%;
|
|
}
|
|
body{
|
|
margin:0;
|
|
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
|
background:var(--background-color);
|
|
color:var(--text-color);
|
|
display:flex;
|
|
flex-direction:column;
|
|
}
|
|
|
|
/* Keep the page breathable but allow full-width editor */
|
|
.main-container{
|
|
flex:1;
|
|
padding: 1rem;
|
|
display:flex;
|
|
flex-direction:column;
|
|
gap:1rem;
|
|
}
|
|
|
|
h1{
|
|
text-align:center;
|
|
color:var(--primary-color);
|
|
margin: .25rem 0 0;
|
|
font-size:1.5rem;
|
|
}
|
|
|
|
/* Card that fills the viewport height */
|
|
.card{
|
|
background:var(--card-background);
|
|
border-radius:var(--border-radius);
|
|
box-shadow:var(--box-shadow);
|
|
border:1px solid var(--border-color);
|
|
display:flex;
|
|
flex-direction:column;
|
|
min-height: 0; /* Allow children to flex */
|
|
height: calc(100vh - 160px); /* fills most of the viewport; adjust if navbar taller */
|
|
/* On very small screens fallback a bit shorter */
|
|
}
|
|
|
|
.card-header{
|
|
font-weight:600;
|
|
padding: .9rem 1.2rem;
|
|
border-bottom:1px solid var(--border-color);
|
|
background:#f9f9f9;
|
|
border-top-left-radius:var(--border-radius);
|
|
border-top-right-radius:var(--border-radius);
|
|
}
|
|
|
|
/* Editor shell: sticky toolbar + flex textarea */
|
|
.editor-toolbar{
|
|
display:flex;
|
|
gap:.75rem;
|
|
align-items:center;
|
|
padding: .75rem 1rem;
|
|
border-bottom:1px solid var(--border-color);
|
|
background: #fff;
|
|
position: sticky;
|
|
top: 0; /* Sticks under the navbar within the card */
|
|
z-index: 2;
|
|
}
|
|
|
|
.editor-toolbar label{
|
|
font-size:.95rem;
|
|
white-space:nowrap;
|
|
}
|
|
|
|
.input-text{
|
|
flex:1;
|
|
min-width: 0;
|
|
padding:.6rem .7rem;
|
|
border:1px solid var(--border-color);
|
|
border-radius:var(--border-radius);
|
|
font-size: .95rem;
|
|
background:#fff;
|
|
}
|
|
|
|
.btn{
|
|
padding:.65rem 1.1rem;
|
|
border:none;
|
|
border-radius:var(--border-radius);
|
|
background:var(--primary-color);
|
|
color:#fff;
|
|
font-weight:600;
|
|
cursor:pointer;
|
|
transition: background-color .15s ease-in-out;
|
|
}
|
|
.btn:hover{ background:var(--primary-hover); }
|
|
.btn.secondary{
|
|
background:#e9ecef; color:#222;
|
|
}
|
|
.btn.secondary:hover{ background:#dfe3e7; }
|
|
|
|
/* The editor itself fills remaining space */
|
|
.editor-area{
|
|
flex:1;
|
|
min-height: 0;
|
|
display:flex;
|
|
padding: 0 1rem 1rem;
|
|
}
|
|
|
|
textarea{
|
|
width:100%;
|
|
height:100%;
|
|
resize:none; /* Keep layout stable; user gets max space already */
|
|
border:1px solid var(--border-color);
|
|
border-radius:var(--border-radius);
|
|
padding: .9rem 1rem;
|
|
box-sizing:border-box;
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
outline:none;
|
|
background:#fff;
|
|
}
|
|
|
|
/* Compact helper row for mobile */
|
|
@media (max-width: 640px){
|
|
.editor-toolbar{
|
|
flex-wrap:wrap;
|
|
}
|
|
.btn{
|
|
flex:0 0 auto;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="navbar"></div>
|
|
|
|
<div class="main-container">
|
|
<h1>Edit File</h1>
|
|
|
|
<div class="card">
|
|
<div class="card-header">Editor</div>
|
|
|
|
<form name="edit-file" action="/files/save" onsubmit="return onSubmitEdit(event)" style="display:flex; flex-direction:column; min-height:0; flex:1;">
|
|
<!-- Sticky toolbar -->
|
|
<div class="editor-toolbar">
|
|
<label for="save-path">File Name:</label>
|
|
<input type="text" id="save-path" class="input-text" value="{{SAVE_PATH_INPUT}}" autocomplete="off" spellcheck="false">
|
|
<button type="submit" class="btn" id="submit-edit">Save</button>
|
|
<button type="button" class="btn secondary" id="cancel">Cancel</button>
|
|
</div>
|
|
|
|
<!-- Stretchy editor space -->
|
|
<div class="editor-area">
|
|
<textarea name="edit-textarea" id="edit-textarea" wrap="off" placeholder="File contents is empty..."></textarea>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Load navbar
|
|
fetch('/www/navbar.html')
|
|
.then(r => r.text())
|
|
.then(html => { document.getElementById('navbar').innerHTML = html; });
|
|
|
|
// Elements
|
|
const txt = document.getElementById('edit-textarea');
|
|
const pathInput = document.getElementById('save-path');
|
|
const btnCancel = document.getElementById('cancel');
|
|
|
|
// Track unsaved changes
|
|
let dirty = false;
|
|
txt.addEventListener('input', () => dirty = true);
|
|
pathInput.addEventListener('input', () => dirty = true);
|
|
window.addEventListener('beforeunload', (e) => {
|
|
if (dirty) {
|
|
e.preventDefault();
|
|
e.returnValue = '';
|
|
}
|
|
});
|
|
|
|
// Keyboard shortcut: Ctrl/Cmd + S
|
|
window.addEventListener('keydown', (e) => {
|
|
const sKey = (e.key === 's' || e.key === 'S');
|
|
if (sKey && (e.ctrlKey || e.metaKey)) {
|
|
e.preventDefault();
|
|
document.getElementById('submit-edit').click();
|
|
}
|
|
});
|
|
|
|
// Cancel -> back to files
|
|
btnCancel.addEventListener('click', () => {
|
|
if (!dirty || confirm('Discard unsaved changes?')) {
|
|
window.location.href = '/files';
|
|
}
|
|
});
|
|
|
|
// Initial file load
|
|
window.addEventListener('load', loadEditFile);
|
|
|
|
function loadEditFile(){
|
|
const savePath = pathInput.value;
|
|
if (savePath !== "/new.txt") {
|
|
fetch(savePath)
|
|
.then(response => {
|
|
if (!response.ok) throw new Error('Failed to fetch file');
|
|
// Try to keep binary files from corrupting layout; still load as text.
|
|
return response.text();
|
|
})
|
|
.then(contents => {
|
|
txt.value = contents;
|
|
pathInput.disabled = true;
|
|
dirty = false;
|
|
})
|
|
.catch(console.error);
|
|
} else {
|
|
// New file: keep editable path
|
|
pathInput.disabled = false;
|
|
}
|
|
}
|
|
|
|
// Validation used by submit
|
|
function validateFilename(){
|
|
const allowedExtensions = "{{ALLOWED_EXTENSIONS_EDIT}}";
|
|
const val = pathInput.value || "";
|
|
const dotIndex = val.lastIndexOf(".") + 1;
|
|
const ext = val.substring(dotIndex);
|
|
const beginsWithSlash = val.startsWith("/");
|
|
|
|
if (!val) { alert("Enter the file name!\n(e.g. /new.txt)"); return false; }
|
|
if (!beginsWithSlash) { alert("The slash at the beginning of the file is missing!"); return false; }
|
|
if (dotIndex === 0 || !ext) { alert("The extension is missing at the end of the file!"); return false; }
|
|
if (allowedExtensions.indexOf(ext) === -1) { alert("Extension not supported!"); return false; }
|
|
return true;
|
|
}
|
|
|
|
// Submit handler: POST with a standard form submit (keeps server behavior)
|
|
function onSubmitEdit(e){
|
|
if (!validateFilename()) { e.preventDefault(); return false; }
|
|
|
|
// Create a transient hidden textarea named exactly as the backend expects if needed.
|
|
// Here the main <textarea> already has the correct name attribute, so just proceed.
|
|
dirty = false; // prevent unload warning on successful submit
|
|
return true;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|