Add contact page/includes and refine header action/logo styling
This commit is contained in:
parent
613d7e15a3
commit
be9da9fe86
7 changed files with 404 additions and 231 deletions
190
new.php
190
new.php
|
|
@ -1040,8 +1040,8 @@ $editDir = null; $editStatus = ''; $editRunning = false; $editData = ['title'=>'
|
|||
if ($editName !== '' || $editId !== '') { try { $editDir = resolve_input_dir_from_request($config['uploads_dir'], $editId, $editName, $articleIndex); $editStatus = input_dir_status($editDir); $editRunning = input_dir_running($editDir); if (!$editRunning) { $editData = read_data($editDir); $editFiles = media_sort_files($editDir, list_files($editDir)); } } catch (Throwable $e) { $err = $e->getMessage(); } }
|
||||
$runningJobs = active_worker_jobs($config);
|
||||
?>
|
||||
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>MVL Admin</title><link rel="icon" type="image/png" href="assets/img/moto_travel.png"><link rel="stylesheet" href="style.css"><style>.admin-search-form{display:flex;flex-wrap:nowrap;align-items:center;gap:.55rem;margin:0 0 .6rem;overflow-x:auto;padding-bottom:2px}.admin-search-wrap{position:relative;min-width:180px;flex:1 1 auto}.admin-search-wrap input{margin:0;padding-right:2.15rem}.admin-search-clear{position:absolute;right:.45rem;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:50%;background:#C46A3A;color:#111315;text-decoration:none;font-size:1rem;line-height:1;font-weight:700;box-shadow:0 1px 4px #0007;transition:opacity .15s ease,transform .15s ease}.admin-search-clear:hover{background:#d97b48;color:#111315;transform:translateY(-50%) scale(1.05)}.admin-search-clear.is-empty{opacity:.38;pointer-events:none}.admin-search-submit{display:inline-grid;place-items:center;width:2.35rem;height:2.35rem;padding:0;border-radius:.5rem;background:#C46A3A;color:#111315;flex:0 0 auto}.admin-search-submit .icon{font-size:1.02rem;line-height:1;transform:translateY(.01em)}.admin-search-filters{display:flex;flex-wrap:nowrap;gap:.7rem;align-items:center;flex:0 0 auto;white-space:nowrap}.admin-filter-item{display:inline-flex;align-items:center;gap:.35rem;margin:0;white-space:nowrap;font-size:.92rem}.admin-filter-item input[type=checkbox]{width:.9rem;height:.9rem;margin:0}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.description-teaser{margin:.2rem 0 .45rem;color:#eadfca}.description-quote{margin:.2rem 0 .45rem;font-style:italic;font-size:.82rem;color:#d4d7dd}</style></head><body>
|
||||
<header class="site-header admin-header"><div class="brand-wrap"><a class="header-logo" href="index.php" aria-label="MVLog home"><img src="assets/img/moto_travel.png" alt=""></a><a class="brand" href="index.php"><h1 style="font-size:1.15rem">MVLog <span class="admin-word">Admin</span></h1><p>journal of an<br>unreliable narrator.</p></a></div></header>
|
||||
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>MVL Admin</title><link rel="icon" type="image/png" href="assets/img/moto_travel.png"><link rel="stylesheet" href="style.css?v=20260531k"><style>.admin-search-form{display:flex;flex-wrap:nowrap;align-items:center;gap:.55rem;margin:0 0 .6rem;overflow-x:auto;padding-bottom:2px}.admin-search-wrap{position:relative;min-width:180px;flex:1 1 auto}.admin-search-wrap input{margin:0;padding-right:2.15rem}.admin-search-clear{position:absolute;right:.45rem;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:50%;background:#C46A3A;color:#111315;text-decoration:none;font-size:1rem;line-height:1;font-weight:700;box-shadow:0 1px 4px #0007;transition:opacity .15s ease,transform .15s ease}.admin-search-clear:hover{background:#d97b48;color:#111315;transform:translateY(-50%) scale(1.05)}.admin-search-clear.is-empty{opacity:.38;pointer-events:none}.admin-search-submit{display:inline-grid;place-items:center;width:2.35rem;height:2.35rem;padding:0;border-radius:.5rem;background:#C46A3A;color:#111315;flex:0 0 auto}.admin-search-submit .icon{font-size:1.02rem;line-height:1;transform:translateY(.01em)}.admin-search-filters{display:flex;flex-wrap:nowrap;gap:.7rem;align-items:center;flex:0 0 auto;white-space:nowrap}.admin-filter-item{display:inline-flex;align-items:center;gap:.35rem;margin:0;white-space:nowrap;font-size:.92rem}.admin-filter-item input[type=checkbox]{width:.9rem;height:.9rem;margin:0}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.description-teaser{margin:.2rem 0 .45rem;color:#eadfca}.description-quote{margin:.2rem 0 .45rem;font-style:italic;font-size:.82rem;color:#d4d7dd}</style></head><body>
|
||||
<header class="site-header admin-header"><div class="brand-wrap"><a class="header-logo" href="index.php" aria-label="MVLog home"><img src="assets/img/moto_travel.png" alt=""></a><a class="brand" href="index.php"><h1 style="font-size:1.15rem">MVLog <span class="admin-word">Admin</span></h1><p>journal of an<br>unreliable narrator.</p></a></div><div class="header-actions"><a class="contact-link" href="contact.php" aria-label="Contact" title="Contact"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6zm-2 0-8 5-8-5h16zm0 12H4V8l8 5 8-5v10z"/></svg></a></div></header>
|
||||
<main>
|
||||
<?php if($err): ?><div class="err"><?=h($err)?></div><?php endif; ?>
|
||||
|
||||
|
|
@ -1553,187 +1553,5 @@ document.addEventListener('DOMContentLoaded', function(){
|
|||
});
|
||||
</script></main>
|
||||
<footer>Input dirs are saved under <code>in-dir/</code>.</footer>
|
||||
<footer class="site-footer admin-footer"><div class="admin-left"><nav class="tabs"><a class="<?= $tab==='new'?'active':'' ?>" href="new.php?tab=new">New</a><a class="<?= $tab==='edit'?'active':'' ?>" href="new.php?tab=edit">Edit</a><a class="<?= $tab==='videos'?'active':'' ?>" href="new.php?tab=videos">Videos</a></nav></div><div id="job-status" class="job-status"<?= empty($runningJobs) ? ' hidden' : '' ?>><?php foreach($runningJobs as $job): ?><span class="job-dot"></span><span><?=h(str_replace('_', ' ', $job['name']))?></span><?php if(!empty($job['started_at'])): ?><span class="job-age">(<?=h(format_job_age($job['started_at']))?>)</span><?php endif; ?><?php endforeach; ?></div><div class="admin-right"><a class="logout-link" href="logout.php">Log off</a></div></footer>
|
||||
<script>document.querySelectorAll('textarea[data-counter]').forEach(function(t){var c=document.getElementById(t.dataset.counter);function u(){c.textContent=t.value.length+' / '+t.maxLength+' characters';}t.addEventListener('input',u);u();});</script>
|
||||
<script>
|
||||
(function(){
|
||||
function attachDescribeButtons(){
|
||||
document.querySelectorAll('.admin-video-row').forEach(function(row){
|
||||
if (row.querySelector('.describe-button')) return;
|
||||
var actions = row.querySelector('.admin-actions');
|
||||
if (!actions) return;
|
||||
var job = row.dataset.job || '';
|
||||
var articleId = row.dataset.id || '';
|
||||
if (!job && !articleId) return;
|
||||
var describe = document.createElement('a');
|
||||
describe.className = 'button describe-button';
|
||||
describe.href = '#';
|
||||
describe.dataset.job = job;
|
||||
describe.dataset.id = articleId;
|
||||
describe.textContent = 'Describe';
|
||||
var first = actions.querySelector('.button');
|
||||
if (first) actions.insertBefore(describe, first);
|
||||
else actions.appendChild(describe);
|
||||
});
|
||||
}
|
||||
|
||||
function showModal(job, articleId, description, teaser, quoteDa, rawJson){
|
||||
var existing = document.getElementById('mvlog-gemini-modal');
|
||||
if (existing) existing.remove();
|
||||
var overlay = document.createElement('div');
|
||||
overlay.id = 'mvlog-gemini-modal';
|
||||
overlay.style.cssText = 'position:fixed;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:9999;';
|
||||
var box = document.createElement('div');
|
||||
box.style.cssText = 'background:white;color:#111;padding:20px;max-width:900px;max-height:80vh;overflow:auto;border-radius:6px;font-family:inherit;';
|
||||
var h = document.createElement('h3');
|
||||
h.textContent = 'Generated description';
|
||||
var quoteBox = null;
|
||||
if (quoteDa) {
|
||||
quoteBox = document.createElement('div');
|
||||
quoteBox.style.cssText = 'white-space:pre-wrap;font-style:italic;font-family:inherit;margin-top:10px;border:1px solid #eee;padding:10px;background:#f4f0ff;border-radius:4px;';
|
||||
quoteBox.textContent = '“' + quoteDa + '”';
|
||||
}
|
||||
var pre = document.createElement('div');
|
||||
pre.style.cssText = 'white-space:pre-wrap;font-family:inherit;margin-top:10px;border:1px solid #eee;padding:12px;background:#f8f8f8;border-radius:4px;';
|
||||
pre.textContent = description || '';
|
||||
var teaserBox = null;
|
||||
if (teaser) {
|
||||
teaserBox = document.createElement('div');
|
||||
teaserBox.style.cssText = 'white-space:pre-wrap;font-family:inherit;margin-top:10px;border:1px solid #eee;padding:10px;background:#fff7e8;border-radius:4px;';
|
||||
teaserBox.textContent = teaser;
|
||||
}
|
||||
var btnBar = document.createElement('div');
|
||||
btnBar.style.cssText = 'margin-top:12px;text-align:right;';
|
||||
var useBtn = document.createElement('button');
|
||||
useBtn.className = 'button';
|
||||
useBtn.textContent = 'Use in form';
|
||||
var cancelBtn = document.createElement('button');
|
||||
cancelBtn.className = 'button';
|
||||
cancelBtn.textContent = 'Close';
|
||||
cancelBtn.style.marginLeft = '8px';
|
||||
btnBar.appendChild(useBtn);
|
||||
btnBar.appendChild(cancelBtn);
|
||||
box.appendChild(h);
|
||||
if (teaserBox) box.appendChild(teaserBox);
|
||||
if (quoteBox) box.appendChild(quoteBox);
|
||||
box.appendChild(pre);
|
||||
box.appendChild(btnBar);
|
||||
overlay.appendChild(box);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
cancelBtn.addEventListener('click', function(){ overlay.remove(); });
|
||||
useBtn.addEventListener('click', function(){
|
||||
var editForm = document.getElementById('edit-form');
|
||||
if (editForm){
|
||||
var idInput = editForm.querySelector('input[name="id"]');
|
||||
var dirInput = editForm.querySelector('input[name="dir"]');
|
||||
var sameById = !!(articleId && idInput && idInput.value === articleId);
|
||||
var sameByDir = !!(job && dirInput && dirInput.value === job);
|
||||
if (sameById || sameByDir){
|
||||
var textarea = editForm.querySelector('textarea[name="description"]');
|
||||
if (textarea){
|
||||
textarea.value = description || '';
|
||||
var teaserInput = editForm.querySelector('input[name="teaser"]');
|
||||
if (teaserInput && teaser) teaserInput.value = teaser;
|
||||
var quoteInput = editForm.querySelector('input[name="quote_da"]');
|
||||
if (quoteInput && quoteDa) quoteInput.value = quoteDa;
|
||||
showToast('Inserted generated description' + (teaser || quoteDa ? ' and extras' : '') + ' into form. Click Save changes to apply.', true);
|
||||
overlay.remove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
var storageKey = 'mvlog_gemini_description_' + (articleId || job);
|
||||
try { localStorage.setItem(storageKey, JSON.stringify({description: description || '', teaser: teaser || '', quote_da: quoteDa || ''})); } catch(e){}
|
||||
var href = 'new.php?tab=edit';
|
||||
if (articleId) href += '&id=' + encodeURIComponent(articleId);
|
||||
if (job) href += '&edit=' + encodeURIComponent(job);
|
||||
window.location.href = href;
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('click', function(e){
|
||||
var el = (e.target && e.target.closest && e.target.closest('.describe-button')) || (e.target && e.target.classList && e.target.classList.contains && e.target.classList.contains('describe-button') ? e.target : null);
|
||||
if (!el) return;
|
||||
e.preventDefault();
|
||||
var job = el.dataset.job || '';
|
||||
var articleId = el.dataset.id || '';
|
||||
if (!job && !articleId) return;
|
||||
el.classList.add('disabled');
|
||||
var oldText = el.textContent;
|
||||
el.textContent = 'Generating...';
|
||||
fetch('lib/generate_data_sync.php', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {'Content-Type':'application/x-www-form-urlencoded'},
|
||||
body: 'id=' + encodeURIComponent(articleId) + '&job=' + encodeURIComponent(job) + '&max_frames=16'
|
||||
}).then(function(r){ return r.json().catch(()=>({})); })
|
||||
.then(function(data){
|
||||
if (data && data.status === 'ok'){
|
||||
var desc = data.description || (data.raw_json && JSON.stringify(data.raw_json)) || '';
|
||||
var teaser = data.teaser || '';
|
||||
var rawFirst = (Array.isArray(data.raw_json) && data.raw_json.length) ? data.raw_json[0] : data.raw_json;
|
||||
var quoteDa = data.quote_da || (rawFirst && rawFirst.quote_da) || '';
|
||||
showModal(job, articleId, desc, teaser, quoteDa, data.raw_json || null);
|
||||
} else {
|
||||
var msg = (data && data.message) ? data.message : 'No response';
|
||||
if (data && data.log) {
|
||||
try {
|
||||
var log = String(data.log);
|
||||
var lines = log.split('\n').map(function(l){ return l.trim(); }).filter(function(l){ return l.length>0; });
|
||||
var keywords = ['quota','rate limit','rate-limit','429','503','overloaded','high demand','busy','unavailable','error','failed','retry'];
|
||||
var found = lines.find(function(l){ var ll=l.toLowerCase(); return keywords.some(function(k){ return ll.indexOf(k) !== -1; }); });
|
||||
if (found) msg += ' — ' + found;
|
||||
else if (lines.length) msg += ' — ' + lines[0].slice(0,200);
|
||||
} catch(e){}
|
||||
}
|
||||
showToast('Error: ' + msg, false, (data && data.log) ? (typeof data.log === 'string' ? data.log : JSON.stringify(data.log)) : null);
|
||||
}
|
||||
}).catch(function(err){
|
||||
showToast('Request failed: ' + err, false);
|
||||
}).finally(function(){
|
||||
el.classList.remove('disabled');
|
||||
el.textContent = oldText;
|
||||
});
|
||||
});
|
||||
|
||||
attachDescribeButtons();
|
||||
var list = document.querySelector('.admin-list.video-list');
|
||||
if (list) new MutationObserver(function(){ attachDescribeButtons(); }).observe(list, {childList:true, subtree:true});
|
||||
|
||||
// On edit page load: if a generated description is in localStorage, insert it into the form.
|
||||
(function(){
|
||||
var editForm = document.getElementById('edit-form');
|
||||
if (!editForm) return;
|
||||
var dirInput = editForm.querySelector('input[name="dir"]');
|
||||
var idInput = editForm.querySelector('input[name="id"]');
|
||||
if (!dirInput && !idInput) return;
|
||||
var job = dirInput ? dirInput.value : '';
|
||||
var articleId = idInput ? idInput.value : '';
|
||||
try {
|
||||
var key = 'mvlog_gemini_description_' + (articleId || job);
|
||||
var raw = localStorage.getItem(key);
|
||||
if (raw) {
|
||||
var payload = null;
|
||||
try { payload = JSON.parse(raw); } catch(e) { payload = {description: raw, teaser: '', quote_da: ''}; }
|
||||
var desc = (payload && typeof payload === 'object') ? String(payload.description || '') : String(raw || '');
|
||||
var teaser = (payload && typeof payload === 'object') ? String(payload.teaser || '') : '';
|
||||
var quoteDa = (payload && typeof payload === 'object') ? String(payload.quote_da || '') : '';
|
||||
var textarea = editForm.querySelector('textarea[name="description"]');
|
||||
if (textarea) {
|
||||
textarea.value = desc;
|
||||
var teaserInput = editForm.querySelector('input[name="teaser"]');
|
||||
if (teaserInput && teaser) teaserInput.value = teaser;
|
||||
var quoteInput = editForm.querySelector('input[name="quote_da"]');
|
||||
if (quoteInput && quoteDa) quoteInput.value = quoteDa;
|
||||
showToast('Inserted generated description' + (teaser || quoteDa ? ' and extras' : '') + ' into form. Click Save changes to apply.', true);
|
||||
}
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
} catch(e){}
|
||||
})();
|
||||
|
||||
})();
|
||||
</script>
|
||||
<script src="js/map_lightbox.js?v=20260531b" defer></script>
|
||||
</body></html>
|
||||
<?php include __DIR__ . '/_footer_admin.php'; ?>
|
||||
</body></html>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue