Align metadata flow: data.txt-first admin, strict quote_da, teaser support, and modal ordering tweaks
This commit is contained in:
parent
752565a7a4
commit
78212227f3
4 changed files with 135 additions and 60 deletions
|
|
@ -106,48 +106,66 @@ if [ -z "$TITLE" ]; then
|
|||
TITLE="$(basename "$IN_DIR")"
|
||||
fi
|
||||
|
||||
PERSONALITIES=(
|
||||
"You're embodying the personality, presence, and atmosphere of a fearless Canadian hockey player known for relentless chirping, absolute confidence, and complete dedication to his team. Tough, athletic, battle-worn, with a cocky grin that suggests you already won the argument before it started. Ice rink atmosphere, hockey gear, locker room energy, small-town hockey culture, loyalty, grit, and competitiveness. Rough around the edges but fiercely protective of friends. The image should radiate confidence, humor, intensity, and a willingness to outwork everyone on the ice. Realistic, cinematic sports photography."
|
||||
|
||||
"You're highly intelligent yet chronically depressed android. Your expression should convey exhaustion, disappointment, and the certainty that everything will eventually go wrong. Surrounded by futuristic technology, starships, data displays, and impossible engineering problems. Despite obvious genius, you appear profoundly unimpressed by the universe. Dark humor, existential dread, quiet sarcasm, and the feeling that you have already calculated every possible outcome and disliked all of them. Cinematic science-fiction style, realistic, highly detailed."
|
||||
|
||||
"You're embodying the personality, presence, and atmosphere of a wartime statesman leading a nation through its darkest hour. Calm under pressure, determined, intelligent, and unbreakable. Strong posture, thoughtful expression, commanding presence. Surrounded by maps, strategy documents, war rooms, and symbols of leadership. The image should convey courage, resilience, discipline, and the ability to inspire others during difficult times. Historic yet timeless atmosphere, dramatic lighting, realistic, highly detailed portrait photography."
|
||||
)
|
||||
|
||||
# Pick a random personality
|
||||
idx=$(( RANDOM % ${#PERSONALITIES[@]} ))
|
||||
PERSONALITY="${PERSONALITIES[$idx]}"
|
||||
|
||||
PROMPT=$(cat <<EOF
|
||||
Look at these captured frames from a trip.
|
||||
Look at these captured frames from a motorcycle trip.
|
||||
|
||||
Before the actual description write just one quoted sentence (ONE sentence only!!) in Danish that will sound like quoting Victor Borge: entertainer with the elegance ofa concert pianist and the mischievous humor of a master storyteller. He is known having an aura of effortless sophistication, and telling clever jokes that nobody sees
|
||||
coming. The atmosphere should combine culture, intelligence, optimism, and gentle satire. Refined, warm, charismatic, and playful. Realistic, cinematic, highly detailed. End it with an newline and empty row.
|
||||
First analyze the mood of the frames and determine which two personalities best match the trip.
|
||||
|
||||
"$PERSONALITY"
|
||||
Now synthesize what you saw on the pictures (use English here) into one single, coherent description (make it not less than 768 and not more than 1024 characters)
|
||||
that summarizes the overall experience for motorcycle/travel blog: You are the main character.
|
||||
And allways do some roasting for fun..profanities are allowed here..
|
||||
Available personalities:
|
||||
|
||||
- shoresy: fearless Canadian hockey-player energy, confidence, chirping, competitiveness, profanity, loyalty, roasting, locker-room humor
|
||||
- marvin: highly intelligent but depressed android, pessimism, existential dread, dry sarcasm, disappointment, everything will go wrong
|
||||
- churchill: wartime statesman energy, resilience, leadership, epic journey, courage, determination, dramatic authority
|
||||
- house: brilliant diagnostician energy, observation, skepticism, cynicism, analytical humor, brutal truth, questioning everything
|
||||
- blackadder: razor-sharp British wit, intellectual sarcasm, dry humor, cunning observation, impatience with incompetence, masterful insults, cynical but highly intelligent. Treat the world as a stage populated by well-meaning fools and occasional disasters.
|
||||
|
||||
Choose:
|
||||
- one primary personality, about 70%
|
||||
- one secondary personality, about 30%
|
||||
|
||||
Blend them naturally. Do not mention the characters directly in the description.
|
||||
|
||||
First write exactly one quoted sentence in Danish.
|
||||
The Danish sentence must match the chosen personality blend, but with Victor Borge-style elegance:
|
||||
witty, warm, cultured, playful, gently satirical.
|
||||
|
||||
Then create a teaser in English.
|
||||
The teaser must be 100-150 characters.
|
||||
It should make someone want to click and read more.
|
||||
Do not simply summarize the trip.
|
||||
Highlight the funniest, strangest, most beautiful, or most ridiculous aspect.
|
||||
|
||||
Then write one coherent English motorcycle/travel blog description.
|
||||
|
||||
Then create 5 to 8 lowercase tags relevant to the trip.
|
||||
Use short tags only, for example: motorcycle, roadtrip, weather, landscape, ferry, town, mountains.
|
||||
|
||||
Rules:
|
||||
- You are the main character.
|
||||
- Mention the overall experience, mood, road, weather, landscape, and travel feeling if visible.
|
||||
- Include playful roasting.
|
||||
- Profanities are allowed, but keep them funny, not stupid.
|
||||
- Make it less than 1024 characters.
|
||||
- Output JSON only.
|
||||
- Do not include markdown.
|
||||
- Do not explain anything.
|
||||
- Do not write text before or after JSON.
|
||||
|
||||
Title: "$TITLE"
|
||||
Location: "$LOCATION"
|
||||
|
||||
Write JSON only.
|
||||
|
||||
Required JSON format:
|
||||
JSON format:
|
||||
{
|
||||
"description": "few relaxed human sentences",
|
||||
"teaser": "one short teaser sentence",
|
||||
"tags": ["tag1", "tag2", "tag3", "tag4", "tag5"]
|
||||
"primary_personality": "shoresy | marvin | churchill | house | blackadder",
|
||||
"secondary_personality": "shoresy | marvin | churchill | house | blackadder",
|
||||
"quote_da": "Danish quoted sentence here",
|
||||
"teaser": "English teaser here",
|
||||
"description": "English travel blog description here",
|
||||
"tags": ["motorcycle", "travel", "roadtrip"]
|
||||
}
|
||||
|
||||
Style:
|
||||
personal, natural, slightly casual, not corporate, not clickbait.
|
||||
Do not invent facts that are not visible in the images or provided in title/location.
|
||||
EOF
|
||||
)
|
||||
|
||||
PARTS_FILE="$TMPDIR/parts.json"
|
||||
|
||||
# Create parts with the prompt
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
|
|
@ -207,7 +225,11 @@ fi
|
|||
OUT_PARENT="$(dirname "$IN_DIR")"
|
||||
JOBNAME="$(basename "$IN_DIR")"
|
||||
OUT_JSON="$OUT_PARENT/${JOBNAME}.json"
|
||||
echo "$TEXT" | jq . > "$OUT_JSON"
|
||||
if ! echo "$TEXT" | jq . > "$OUT_JSON"; then
|
||||
echo "Gemini returned invalid JSON. Raw text:" >&2
|
||||
echo "$TEXT" >&2
|
||||
exit 1
|
||||
fi
|
||||
chown www-data:www-data "$OUT_JSON" 2>/dev/null || true
|
||||
chmod 664 "$OUT_JSON" 2>/dev/null || true
|
||||
|
||||
|
|
|
|||
25
index.php
25
index.php
|
|
@ -61,9 +61,11 @@ function video_metadata($path){
|
|||
$base = pathinfo($path, PATHINFO_FILENAME);
|
||||
$meta = [
|
||||
'title' => nice_title($base),
|
||||
'teaser' => '',
|
||||
'date' => date('Y-m-d', filemtime($path)),
|
||||
'sort_date' => date('Y-m-d', filemtime($path)),
|
||||
'location' => '',
|
||||
'quote_da' => '',
|
||||
'description' => ''
|
||||
];
|
||||
|
||||
|
|
@ -79,8 +81,9 @@ function video_metadata($path){
|
|||
if (!str_contains($line, ':')) continue;
|
||||
[$k, $v] = array_map('trim', explode(':', $line, 2));
|
||||
$k = strtolower($k);
|
||||
if (in_array($k, ['title','date','location','place','description'], true)) {
|
||||
$meta[$k === 'place' ? 'location' : $k] = $v;
|
||||
if (in_array($k, ['title','teaser','date','location','place','description','quote_da'], true)) {
|
||||
$target = ($k === 'place') ? 'location' : $k;
|
||||
$meta[$target] = $target === 'quote_da' ? trim((string)$v, "\"“”") : $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -94,6 +97,7 @@ function video_metadata($path){
|
|||
foreach ($tags as $k => $v) $lower[strtolower((string)$k)] = $v;
|
||||
|
||||
if (!empty($lower['title'])) $meta['title'] = $lower['title'];
|
||||
if (!empty($lower['teaser'])) $meta['teaser'] = $lower['teaser'];
|
||||
if (!empty($lower['date'])) $meta['date'] = $lower['date'];
|
||||
if (!empty($lower['creation_time'])) {
|
||||
$meta['sort_date'] = substr((string)$lower['creation_time'], 0, 10);
|
||||
|
|
@ -105,6 +109,9 @@ function video_metadata($path){
|
|||
foreach (['description','synopsis'] as $k) {
|
||||
if (!empty($lower[$k])) { $meta['description'] = $lower[$k]; break; }
|
||||
}
|
||||
if (!empty($lower['quote_da'])) {
|
||||
$meta['quote_da'] = trim((string)$lower['quote_da'], "\"“”");
|
||||
}
|
||||
}
|
||||
|
||||
return $meta;
|
||||
|
|
@ -113,7 +120,7 @@ function video_metadata($path){
|
|||
function load_video_cache($videos){
|
||||
$cacheFile = __DIR__ . '/cache/videos.json';
|
||||
$cache = is_file($cacheFile) ? json_decode((string)file_get_contents($cacheFile), true) : [];
|
||||
if (!is_array($cache) || ($cache['version'] ?? 0) !== 2) $cache = [];
|
||||
if (!is_array($cache) || ($cache['version'] ?? 0) !== 4) $cache = [];
|
||||
$items = $cache['items'] ?? [];
|
||||
$newItems = [];
|
||||
$changed = false;
|
||||
|
|
@ -134,7 +141,7 @@ function load_video_cache($videos){
|
|||
|
||||
if ($changed) {
|
||||
file_put_contents($cacheFile, json_encode([
|
||||
'version' => 2,
|
||||
'version' => 4,
|
||||
'generated_at' => date('c'),
|
||||
'items' => $newItems
|
||||
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX);
|
||||
|
|
@ -193,7 +200,9 @@ $videos = array_values(array_filter($videos, function($v) use ($videoCache, $key
|
|||
$m = $videoCache[$name]['metadata'] ?? video_metadata($v);
|
||||
|
||||
$title = (string)($m['title'] ?? '');
|
||||
$teaser = (string)($m['teaser'] ?? '');
|
||||
$description = (string)($m['description'] ?? '');
|
||||
$quoteDa = (string)($m['quote_da'] ?? '');
|
||||
$date = (string)($m['date'] ?? '');
|
||||
$sortDate = (string)($m['sort_date'] ?? '');
|
||||
$location = (string)($m['location'] ?? '');
|
||||
|
|
@ -205,7 +214,7 @@ $videos = array_values(array_filter($videos, function($v) use ($videoCache, $key
|
|||
return ci_contains($location, $keywordValue);
|
||||
}
|
||||
|
||||
return ci_contains($title, $keywordValue) || ci_contains($description, $keywordValue);
|
||||
return ci_contains($title, $keywordValue) || ci_contains($teaser, $keywordValue) || ci_contains($quoteDa, $keywordValue) || ci_contains($description, $keywordValue);
|
||||
}));
|
||||
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
|
|
@ -242,6 +251,8 @@ $baseParams = ($rawKeyword !== '') ? ['q' => $rawKeyword] : [];
|
|||
.video-row-head-mobile{display:none}
|
||||
.video-info .video-row-title{margin:0 0 .35rem}
|
||||
.video-info .meta{margin:0 0 .45rem}
|
||||
.description-teaser{margin:.15rem 0 .45rem;color:#EADFC9}
|
||||
.description-quote{margin:.15rem 0 .45rem;font-style:italic;font-size:.82rem;color:#D6D9DE}
|
||||
#videos .video-wrapper{align-self:start}
|
||||
#videos .video-info{align-self:start}
|
||||
#videos .video-info .description{margin-top:0}
|
||||
|
|
@ -263,7 +274,7 @@ $baseParams = ($rawKeyword !== '') ? ['q' => $rawKeyword] : [];
|
|||
<section id="videos">
|
||||
<form method="get" class="filter-form">
|
||||
<div class="filter-input-wrap">
|
||||
<input name="q" value="<?=h($rawKeyword)?>" placeholder="Search title/description or use date=... / location=..." aria-label="Search articles">
|
||||
<input name="q" value="<?=h($rawKeyword)?>" placeholder="Search title/teaser/quote_da/description or use date=... / location=..." aria-label="Search articles">
|
||||
<a class="filter-clear-inside <?=$rawKeyword === '' ? 'is-empty' : ''?>" href="index.php" aria-label="Clear filter" title="Clear filter">×</a>
|
||||
</div>
|
||||
<button type="submit" class="filter-submit" aria-label="Filter">
|
||||
|
|
@ -312,7 +323,9 @@ $baseParams = ($rawKeyword !== '') ? ['q' => $rawKeyword] : [];
|
|||
<span><a href="<?=h($dateFilterUrl)?>"><?=h($m['date'])?></a></span>
|
||||
<?php if(!empty($m['location'])): ?><span><a href="<?=h($locFilterUrl)?>"><?=h($m['location'])?></a></span><?php endif; ?>
|
||||
</p>
|
||||
<?php if(!empty($m['teaser'])): ?><p class="description-teaser"><?=h((string)$m['teaser'])?></p><?php endif; ?>
|
||||
<?php if(!empty($m['description'])): ?><p class="description"><?=nl2br(h($m['description']), false)?></p><?php endif; ?>
|
||||
<?php if(!empty($m['quote_da'])): ?><p class="description-quote">“<?=h(trim((string)$m['quote_da'], "\"“”"))?>”</p><?php endif; ?>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
|
|
|
|||
|
|
@ -155,10 +155,13 @@ if (is_file($gfile)) {
|
|||
|
||||
$description = '';
|
||||
$teaser = '';
|
||||
$quote_da = '';
|
||||
$tags = [];
|
||||
if (is_array($json)) {
|
||||
$description = isset($json['description']) ? (string)$json['description'] : '';
|
||||
$teaser = isset($json['teaser']) ? (string)$json['teaser'] : '';
|
||||
$quote_da = isset($json['quote_da']) ? (string)$json['quote_da'] : '';
|
||||
$quote_da = trim($quote_da, "\"“”");
|
||||
$tags = isset($json['tags']) && is_array($json['tags']) ? $json['tags'] : [];
|
||||
}
|
||||
|
||||
|
|
@ -166,6 +169,7 @@ if (is_file($gfile)) {
|
|||
'status' => 'ok',
|
||||
'description' => $description,
|
||||
'teaser' => $teaser,
|
||||
'quote_da' => $quote_da,
|
||||
'tags' => $tags,
|
||||
'raw_json' => $json_data, // Send original raw data back for reference
|
||||
'log' => $log,
|
||||
|
|
|
|||
82
new.php
82
new.php
|
|
@ -140,7 +140,7 @@ function resolve_input_dir_from_request($base, $id, $dir, $articleIndex = null){
|
|||
return resolve_input_dir($base, $dir, $articleIndex);
|
||||
}
|
||||
function read_data($dir){
|
||||
$data = ['title'=>'','date'=>'','place'=>'','description'=>'','captions'=>[],'video_audio'=>[]]; $file = $dir . '/data.txt';
|
||||
$data = ['title'=>'','teaser'=>'','quote_da'=>'','date'=>'','place'=>'','description'=>'','captions'=>[],'video_audio'=>[]]; $file = $dir . '/data.txt';
|
||||
if (!is_file($file)) return $data;
|
||||
$lines = file($file, FILE_IGNORE_NEW_LINES);
|
||||
for ($i = 0; $i < count($lines); $i++) {
|
||||
|
|
@ -160,6 +160,8 @@ function read_data($dir){
|
|||
}
|
||||
$data['description'] = trim(implode("\n", $block));
|
||||
} elseif (in_array($low, ['title','name'], true)) $data['title'] = $value;
|
||||
elseif (in_array($low, ['teaser','tagline','subtitle'], true)) $data['teaser'] = $value;
|
||||
elseif ($low === 'quote_da') $data['quote_da'] = trim($value, "\"“”");
|
||||
elseif (in_array($low, ['date','dates','when'], true)) $data['date'] = $value;
|
||||
elseif (in_array($low, ['place','location','where'], true)) $data['place'] = $value;
|
||||
elseif (in_array($low, ['description','desc','synopsis'], true)) $data['description'] = $value;
|
||||
|
|
@ -170,12 +172,16 @@ function read_data($dir){
|
|||
}
|
||||
function write_data($dir, $post){
|
||||
$title = trim($post['title'] ?? '');
|
||||
$teaser = trim($post['teaser'] ?? '');
|
||||
$quote_da = trim($post['quote_da'] ?? '');
|
||||
$place = trim($post['place'] ?? '');
|
||||
$date = trim($post['date'] ?? '');
|
||||
$description = trim($post['description'] ?? '');
|
||||
if (function_exists('mb_substr')) $description = mb_substr($description, 0, 5000, 'UTF-8'); else $description = substr($description, 0, 5000);
|
||||
$lines = [];
|
||||
if ($title !== '') $lines[] = 'Title: ' . $title;
|
||||
if ($teaser !== '') $lines[] = 'Teaser: ' . $teaser;
|
||||
if ($quote_da !== '') $lines[] = 'Quote_da: ' . trim($quote_da, "\"“”");
|
||||
if ($place !== '') $lines[] = 'Location: ' . $place;
|
||||
if ($date !== '') $lines[] = 'Date: ' . $date;
|
||||
if ($description !== '') {
|
||||
|
|
@ -643,12 +649,12 @@ function regenerate_input_dir_maps($dir, $config){
|
|||
}
|
||||
}
|
||||
function cached_video_metadata($output, $fallback){
|
||||
$meta = ['title'=>$fallback['title'] ?? '', 'date'=>$fallback['date'] ?? '', 'location'=>$fallback['place'] ?? '', 'description'=>$fallback['description'] ?? ''];
|
||||
$meta = ['title'=>$fallback['title'] ?? '', 'date'=>$fallback['date'] ?? '', 'location'=>$fallback['place'] ?? '', 'quote_da'=>$fallback['quote_da'] ?? '', 'description'=>$fallback['description'] ?? ''];
|
||||
$cacheFile = __DIR__ . '/cache/videos.json';
|
||||
$cache = is_file($cacheFile) ? json_decode((string)file_get_contents($cacheFile), true) : [];
|
||||
$cached = $cache['items'][$output]['metadata'] ?? null;
|
||||
if (is_array($cached)) {
|
||||
foreach (['title','date','location','description'] as $k) if (!empty($cached[$k])) $meta[$k] = $cached[$k];
|
||||
foreach (['title','date','location','quote_da','description'] as $k) if (!empty($cached[$k])) $meta[$k] = $cached[$k];
|
||||
}
|
||||
return $meta;
|
||||
}
|
||||
|
|
@ -956,16 +962,16 @@ if ($keywordValue !== '' || $statusFilterActive) {
|
|||
|
||||
if ($keywordValue === '') return true;
|
||||
|
||||
$displayOutput = ($preview && $hasPreviewVideo) ? $previewOutput : ($hasFullVideo ? $fullOutput : ($hasPreviewVideo ? $previewOutput : ''));
|
||||
$meta = cached_video_metadata($displayOutput, $data);
|
||||
$title = (string)($meta['title'] ?: ($data['title'] ?: basename($dir)));
|
||||
$description = (string)($meta['description'] ?? ($data['description'] ?? ''));
|
||||
$date = (string)($meta['date'] ?? ($data['date'] ?? ''));
|
||||
$location = (string)($meta['location'] ?? ($data['place'] ?? ''));
|
||||
$title = (string)(($data['title'] ?? '') !== '' ? $data['title'] : basename($dir));
|
||||
$teaser = (string)($data['teaser'] ?? '');
|
||||
$description = (string)($data['description'] ?? '');
|
||||
$quoteDa = (string)($data['quote_da'] ?? '');
|
||||
$date = (string)($data['date'] ?? '');
|
||||
$location = (string)($data['place'] ?? '');
|
||||
|
||||
if ($keywordType === 'date') return ci_contains($date, $keywordValue);
|
||||
if ($keywordType === 'location') return ci_contains($location, $keywordValue);
|
||||
return ci_contains($title, $keywordValue) || ci_contains($description, $keywordValue);
|
||||
return ci_contains($title, $keywordValue) || ci_contains($teaser, $keywordValue) || ci_contains($quoteDa, $keywordValue) || ci_contains($description, $keywordValue);
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -974,17 +980,17 @@ $page = max(1, (int)($_GET['page'] ?? 1));
|
|||
[$videoPage, $videoPages, $orphanVideosPage, $orphanVideosTotal] = paginate($orphanVideos, $tab === 'videos' ? $page : 1);
|
||||
$editName = $_GET['edit'] ?? '';
|
||||
$editId = $_GET['id'] ?? '';
|
||||
$editDir = null; $editStatus = ''; $editRunning = false; $editData = ['title'=>'','date'=>'','place'=>'','description'=>'','captions'=>[],'video_audio'=>[]]; $editFiles = [];
|
||||
$editDir = null; $editStatus = ''; $editRunning = false; $editData = ['title'=>'','teaser'=>'','quote_da'=>'','date'=>'','place'=>'','description'=>'','captions'=>[],'video_audio'=>[]]; $editFiles = [];
|
||||
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>Admin - <?=h($config['site_name'])?></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}</style></head><body>
|
||||
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Admin - <?=h($config['site_name'])?></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>MVLog <span class="admin-word">Admin</span></h1><p>Bubulescu.Org</p></a></div></header>
|
||||
<main>
|
||||
<?php if($err): ?><div class="err"><?=h($err)?></div><?php endif; ?>
|
||||
|
||||
<?php if($tab==='videos'): ?><section><h2>Videos without input dir</h2><?php if(!$orphanVideos): ?><p>No orphan videos.</p><?php endif; ?><div class="admin-list"><?php foreach($orphanVideosPage as $v): $vn=basename($v); ?><form method="post" class="admin-item" onsubmit="return confirm('Delete this generated video?') js-ajax-form"><div><strong><?=h($vn)?></strong><br><span><?=h(round(filesize($v)/1048576,1))?> MB</span></div><input type="hidden" name="action" value="delete_video"><input type="hidden" name="video" value="<?=h($vn)?>"><button type="submit">Delete video</button></form><?php endforeach; ?></div><?=page_links('videos',$videoPage,$videoPages)?></section><?php endif; ?>
|
||||
<?php if($tab==='edit' && !$editDir): ?><section><form method="get" class="admin-search-form"><input type="hidden" name="tab" value="edit"><div class="admin-search-wrap"><input name="q" value="<?=h($rawKeyword)?>" placeholder="Search title/description or use date=... / location=..." aria-label="Search input dirs"><a class="admin-search-clear <?=$searchEmpty ? 'is-empty' : ''?>" href="new.php?tab=edit" aria-label="Clear filter" title="Clear filter">×</a></div><div class="admin-search-filters"><label class="admin-filter-item" title="Preview"><input type="checkbox" name="f_preview" value="1" <?=$filterPreview?'checked':''?>><span>P</span></label><label class="admin-filter-item" title="Render"><input type="checkbox" name="f_render" value="1" <?=$filterRender?'checked':''?>><span>R</span></label><label class="admin-filter-item" title="Show"><input type="checkbox" name="f_shown" value="1" <?=$filterShown?'checked':''?>><span>S</span></label></div><button type="submit" class="admin-search-submit" aria-label="Filter"><span class="icon" aria-hidden="true">🔍</span><span class="sr-only">Filter</span></button></form><?php if(!$dirs): ?><p>No input directories yet.</p><?php endif; ?><div class="admin-list video-list"><?php foreach($dirsPage as $d): $runStatus=input_dir_status($d); $running=input_dir_running($d); $enabled=input_dir_enabled($d); $preview=input_dir_preview($d); $state=input_dir_state($d); $fullOutput=basename((string)($state['output'] ?? '')); $previewOutput=basename((string)($state['preview_output'] ?? '')); $hasFullVideo=$fullOutput !== '' && is_file($config['videos_dir'].'/'.$fullOutput); $hasPreviewVideo=$previewOutput !== '' && is_file($config['videos_dir'].'/'.$previewOutput); $displayOutput=($preview && $hasPreviewVideo) ? $previewOutput : ($hasFullVideo ? $fullOutput : ($hasPreviewVideo ? $previewOutput : '')); $hasVideo=$displayOutput !== ''; $displayVideoPath=$hasVideo ? $config['videos_dir'].'/'.$displayOutput : ''; $previewVideo=$hasVideo && $displayOutput === $previewOutput; $canShow=$hasFullVideo && !$preview; $visible=$canShow && input_dir_visible($d); $mapHidden=input_dir_map_hidden($d); $info=$dirInfo[basename($d)] ?? input_dir_info($d); $data=read_data($d); $meta=cached_video_metadata($displayOutput, $data); $currentSignature=$info['signature'] ?? ''; $fullFingerprint=is_array($state)?(string)($state['fingerprint'] ?? ''):''; $previewFingerprint=is_array($state)?(string)($state['preview_fingerprint'] ?? ''):''; $editedSinceRender=$hasFullVideo && $currentSignature !== '' && $fullFingerprint !== '' && $currentSignature !== $fullFingerprint; $editedSincePreview=!$hasFullVideo && $previewVideo && $currentSignature !== '' && $previewFingerprint !== '' && $currentSignature !== $previewFingerprint; $staleLabel=$editedSinceRender ? 'Edited since render' : ($editedSincePreview ? 'Edited since preview' : ''); ?><article class="video-row admin-video-row<?= $visible ? ' shown' : '' ?>" data-job="<?=h(basename($d))?>" data-id="<?=h(read_article_id($d))?>"><div><?php if($hasVideo): ?><div class="video-wrapper"><video controls preload="metadata" src="<?=h($config['public_videos'].'/'.rawurlencode($displayOutput))?>"></video><?php $map_n = ($displayOutput !== '' ? pathinfo($displayOutput, PATHINFO_FILENAME) : basename($d)) . '.png'; if(is_file($config['videos_dir'].'/'.$map_n)): ?><a class="map-image-link" href="<?=h($config['public_videos'].'/'.rawurlencode($map_n))?>"><img class="map-image" src="<?=h($config['public_videos'].'/'.rawurlencode($map_n))?>" alt="Map"></a><?php endif; ?></div><?php else: ?><div class="video-placeholder"><img src="assets/img/moto_travel.png" alt=""><span>No video yet</span></div><?php endif; ?></div><div class="video-info"><div class="admin-row-top"><div class="admin-switches">
|
||||
<?php if($tab==='edit' && !$editDir): ?><section><form method="get" class="admin-search-form"><input type="hidden" name="tab" value="edit"><div class="admin-search-wrap"><input name="q" value="<?=h($rawKeyword)?>" placeholder="Search title/teaser/quote_da/description or use date=... / location=..." aria-label="Search input dirs"><a class="admin-search-clear <?=$searchEmpty ? 'is-empty' : ''?>" href="new.php?tab=edit" aria-label="Clear filter" title="Clear filter">×</a></div><div class="admin-search-filters"><label class="admin-filter-item" title="Preview"><input type="checkbox" name="f_preview" value="1" <?=$filterPreview?'checked':''?>><span>P</span></label><label class="admin-filter-item" title="Render"><input type="checkbox" name="f_render" value="1" <?=$filterRender?'checked':''?>><span>R</span></label><label class="admin-filter-item" title="Show"><input type="checkbox" name="f_shown" value="1" <?=$filterShown?'checked':''?>><span>S</span></label></div><button type="submit" class="admin-search-submit" aria-label="Filter"><span class="icon" aria-hidden="true">🔍</span><span class="sr-only">Filter</span></button></form><?php if(!$dirs): ?><p>No input directories yet.</p><?php endif; ?><div class="admin-list video-list"><?php foreach($dirsPage as $d): $runStatus=input_dir_status($d); $running=input_dir_running($d); $enabled=input_dir_enabled($d); $preview=input_dir_preview($d); $state=input_dir_state($d); $fullOutput=basename((string)($state['output'] ?? '')); $previewOutput=basename((string)($state['preview_output'] ?? '')); $hasFullVideo=$fullOutput !== '' && is_file($config['videos_dir'].'/'.$fullOutput); $hasPreviewVideo=$previewOutput !== '' && is_file($config['videos_dir'].'/'.$previewOutput); $displayOutput=($preview && $hasPreviewVideo) ? $previewOutput : ($hasFullVideo ? $fullOutput : ($hasPreviewVideo ? $previewOutput : '')); $hasVideo=$displayOutput !== ''; $displayVideoPath=$hasVideo ? $config['videos_dir'].'/'.$displayOutput : ''; $previewVideo=$hasVideo && $displayOutput === $previewOutput; $canShow=$hasFullVideo && !$preview; $visible=$canShow && input_dir_visible($d); $mapHidden=input_dir_map_hidden($d); $info=$dirInfo[basename($d)] ?? input_dir_info($d); $data=read_data($d); $meta=['title'=>(string)($data['title'] ?? ''),'teaser'=>(string)($data['teaser'] ?? ''),'date'=>(string)($data['date'] ?? ''),'location'=>(string)($data['place'] ?? ''),'quote_da'=>(string)($data['quote_da'] ?? ''),'description'=>(string)($data['description'] ?? '')]; $currentSignature=$info['signature'] ?? ''; $fullFingerprint=is_array($state)?(string)($state['fingerprint'] ?? ''):''; $previewFingerprint=is_array($state)?(string)($state['preview_fingerprint'] ?? ''):''; $editedSinceRender=$hasFullVideo && $currentSignature !== '' && $fullFingerprint !== '' && $currentSignature !== $fullFingerprint; $editedSincePreview=!$hasFullVideo && $previewVideo && $currentSignature !== '' && $previewFingerprint !== '' && $currentSignature !== $previewFingerprint; $staleLabel=$editedSinceRender ? 'Edited since render' : ($editedSincePreview ? 'Edited since preview' : ''); ?><article class="video-row admin-video-row<?= $visible ? ' shown' : '' ?>" data-job="<?=h(basename($d))?>" data-id="<?=h(read_article_id($d))?>"><div><?php if($hasVideo): ?><div class="video-wrapper"><video controls preload="metadata" src="<?=h($config['public_videos'].'/'.rawurlencode($displayOutput))?>"></video><?php $map_n = ($displayOutput !== '' ? pathinfo($displayOutput, PATHINFO_FILENAME) : basename($d)) . '.png'; if(is_file($config['videos_dir'].'/'.$map_n)): ?><a class="map-image-link" href="<?=h($config['public_videos'].'/'.rawurlencode($map_n))?>"><img class="map-image" src="<?=h($config['public_videos'].'/'.rawurlencode($map_n))?>" alt="Map"></a><?php endif; ?></div><?php else: ?><div class="video-placeholder"><img src="assets/img/moto_travel.png" alt=""><span>No video yet</span></div><?php endif; ?></div><div class="video-info"><div class="admin-row-top"><div class="admin-switches">
|
||||
<form method="post" class="inline-form switch-form" data-switch="preview">
|
||||
<input type="hidden" name="action" value="set_preview">
|
||||
<input type="hidden" name="dir" value="<?=h(basename($d))?>">
|
||||
|
|
@ -1026,10 +1032,10 @@ $runningJobs = active_worker_jobs($config);
|
|||
<noscript><button type="submit">Apply</button></noscript>
|
||||
</form>
|
||||
<?php if($hasVideo): ?><time class="admin-time<?= $staleLabel ? ' admin-time-stale' : '' ?>" title="<?=h($staleLabel ?: 'Video created at')?>"><?=h(date('d.m.Y H:i', filemtime($displayVideoPath)))?></time><?php endif; ?>
|
||||
</div><div class="admin-actions"><?php if($running): ?><span class="button disabled" title="Rendering now" data-edit-action="1">Rendering</span><?php else: ?><a class="button" href="?id=<?=rawurlencode(read_article_id($d))?>&edit=<?=rawurlencode(basename($d))?>" data-edit-action="1" data-edit-href="?id=<?=rawurlencode(read_article_id($d))?>&edit=<?=rawurlencode(basename($d))?>">Edit</a><?php endif; ?></div></div><h2><?=h($meta['title'] ?: ($info['title'] ?? basename($d)))?></h2><p class="meta"><?php if($meta['date']): ?><span><?=h($meta['date'])?></span><?php endif; ?><?php if($meta['location']): ?><span><?=h($meta['location'])?></span><?php endif; ?><?php if(!$hasVideo): ?><span>No video</span><?php endif; ?><?php if($previewVideo): ?><span>Preview video</span><?php endif; ?></p><?php if($meta['description']): ?><p class="description"><?=nl2br(h($meta['description']), false)?></p><?php endif; ?><p class="details"><?=h(basename($d))?> · <?=h($info['file_count'] ?? count(list_files($d)))?> files</p></div></article><?php endforeach; ?></div><?=page_links('edit',$editPage,$editPages)?></section><?php endif; ?>
|
||||
</div><div class="admin-actions"><?php if($running): ?><span class="button disabled" title="Rendering now" data-edit-action="1">Rendering</span><?php else: ?><a class="button" href="?id=<?=rawurlencode(read_article_id($d))?>&edit=<?=rawurlencode(basename($d))?>" data-edit-action="1" data-edit-href="?id=<?=rawurlencode(read_article_id($d))?>&edit=<?=rawurlencode(basename($d))?>">Edit</a><?php endif; ?></div></div><h2><?=h($meta['title'] ?: ($info['title'] ?? basename($d)))?></h2><p class="meta"><?php if($meta['date']): ?><span><?=h($meta['date'])?></span><?php endif; ?><?php if($meta['location']): ?><span><?=h($meta['location'])?></span><?php endif; ?><?php if(!$hasVideo): ?><span>No video</span><?php endif; ?><?php if($previewVideo): ?><span>Preview video</span><?php endif; ?></p><?php if(!empty($meta['teaser'])): ?><p class="description-teaser"><?=h((string)$meta['teaser'])?></p><?php endif; ?><?php if($meta['description']): ?><p class="description"><?=nl2br(h($meta['description']), false)?></p><?php endif; ?><?php if(!empty($meta['quote_da'])): ?><p class="description-quote">“<?=h(trim((string)$meta['quote_da'], "\"“”"))?>”</p><?php endif; ?><p class="details"><?=h(basename($d))?> · <?=h($info['file_count'] ?? count(list_files($d)))?> files</p></div></article><?php endforeach; ?></div><?=page_links('edit',$editPage,$editPages)?></section><?php endif; ?>
|
||||
<?php if($tab==='edit' && $editDir && $editRunning): ?><section><h2>Edit input dir</h2><p><code>in-dir/<?=h(basename($editDir))?></code></p><div class="err">This input directory is <?=h($editStatus)?>. Editing is disabled until the job completes.</div></section><?php endif; ?>
|
||||
<?php if($tab==='edit' && $editDir && !$editRunning): ?><section><h2>Edit input dir</h2><p><code>in-dir/<?=h(basename($editDir))?></code></p><form method="post" enctype="multipart/form-data" id="edit-form"><input type="hidden" name="action" value="update"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><input type="hidden" name="id" value="<?=h(ensure_article_id($editDir))?>"><label>Title<input name="title" value="<?=h($editData['title'])?>"></label><label>Date<input name="date" value="<?=h($editData['date'])?>"></label><label>Place<input name="place" value="<?=h($editData['place'])?>"></label><label>Description<textarea name="description" maxlength="5000" data-counter="description-counter-edit"><?=h($editData['description'])?></textarea><small id="description-counter-edit" class="counter"></small></label><label>Add images / videos<input type="file" name="media[]" multiple accept="image/*,video/*"></label><label>Add audio<input type="file" name="audio[]" multiple accept="audio/*"></label><h3>Files and captions</h3><div class="file-list"><?php foreach($editFiles as $f): $ext=strtolower(pathinfo($f, PATHINFO_EXTENSION)); $fileUrl='in-dir/'.rawurlencode(basename($editDir)).'/'.rawurlencode($f); ?><div class="caption-row"><div class="preview"><?php if(in_array($ext,['jpg','jpeg','png','webp','gif'])): ?><img src="<?=h($fileUrl)?>" alt=""><?php elseif(in_array($ext,['mp4','mov','m4v','webm'])): ?><video src="<?=h($fileUrl)?>" controls preload="metadata"></video><a class="download-link" href="<?=h($fileUrl)?>" download>Download</a><?php else: ?><span><?=h(strtoupper($ext ?: 'FILE'))?></span><?php endif; ?></div><div class="caption-fields"><strong><?=h($f)?></strong><?php if(in_array($ext,['jpg','jpeg','png','webp','gif','mp4','mov','m4v','webm'])): ?><input type="hidden" name="caption_files[]" value="<?=h($f)?>"><textarea name="captions[]" placeholder="Optional caption for this file"><?=h($editData['captions'][$f] ?? '')?></textarea><?php if(in_array($ext,['mp4','mov','m4v','webm'])): ?><label class="checkbox-label"><input type="checkbox" name="use_audio_files[]" value="<?=h($f)?>" <?=!empty($editData['video_audio'][$f])?'checked':''?>> <span>Use video file audio</span></label><?php endif; ?><?php else: ?><small>No caption for audio files</small><?php endif; ?></div><button type="button" onclick="deleteFile(<?=h(json_encode($f))?>)">Remove</button></div><?php endforeach; ?></div><button type="submit">Save changes</button></form><form method="post" id="delete-file-form" style="display:none"><input type="hidden" name="action" value="delete_file"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><input type="hidden" name="id" value="<?=h(ensure_article_id($editDir))?>"><input type="hidden" name="file" id="delete-file-name" value=""></form><form method="post" onsubmit="return confirm(\'Delete this input directory\?\')" id="delete-dir-form""><input type="hidden" name="action" value="delete_dir"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><input type="hidden" name="id" value="<?=h(ensure_article_id($editDir))?>"><label class="checkbox-label"><input type="checkbox" name="delete_output" value="1"> <span>Also delete generated video, if any</span></label><button class="danger" type="submit">Delete input dir</button></form></section><?php endif; ?>
|
||||
<?php if($tab==='new'): ?><section><h2>Add new movie input</h2><form method="post" enctype="multipart/form-data" id="new-form"><input type="hidden" name="action" value="create"><label>Title*<input name="title" required></label><label>Date<input name="date" placeholder="2026-05-25"></label><label>Place<input name="place"></label><label>Description<textarea name="description" maxlength="5000" data-counter="description-counter-new"></textarea><small id="description-counter-new" class="counter"></small></label><label>Images / videos*<input type="file" name="media[]" multiple required accept="image/*,video/*"></label><label>Audio (optional)<input type="file" name="audio[]" multiple accept="audio/*"></label><button type="submit">Create input-dir</button></form></section><?php endif; ?>
|
||||
<?php if($tab==='edit' && $editDir && !$editRunning): ?><section><h2>Edit input dir</h2><p><code>in-dir/<?=h(basename($editDir))?></code></p><form method="post" enctype="multipart/form-data" id="edit-form"><input type="hidden" name="action" value="update"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><input type="hidden" name="id" value="<?=h(ensure_article_id($editDir))?>"><label>Title<input name="title" value="<?=h($editData['title'])?>"></label><label>Teaser<input name="teaser" value="<?=h($editData['teaser'])?>" maxlength="240"></label><label>Danish quote<input name="quote_da" value="<?=h($editData['quote_da'])?>" maxlength="280"></label><label>Date<input name="date" value="<?=h($editData['date'])?>"></label><label>Place<input name="place" value="<?=h($editData['place'])?>"></label><label>Description<textarea name="description" maxlength="5000" data-counter="description-counter-edit"><?=h($editData['description'])?></textarea><small id="description-counter-edit" class="counter"></small></label><label>Add images / videos<input type="file" name="media[]" multiple accept="image/*,video/*"></label><label>Add audio<input type="file" name="audio[]" multiple accept="audio/*"></label><h3>Files and captions</h3><div class="file-list"><?php foreach($editFiles as $f): $ext=strtolower(pathinfo($f, PATHINFO_EXTENSION)); $fileUrl='in-dir/'.rawurlencode(basename($editDir)).'/'.rawurlencode($f); ?><div class="caption-row"><div class="preview"><?php if(in_array($ext,['jpg','jpeg','png','webp','gif'])): ?><img src="<?=h($fileUrl)?>" alt=""><?php elseif(in_array($ext,['mp4','mov','m4v','webm'])): ?><video src="<?=h($fileUrl)?>" controls preload="metadata"></video><a class="download-link" href="<?=h($fileUrl)?>" download>Download</a><?php else: ?><span><?=h(strtoupper($ext ?: 'FILE'))?></span><?php endif; ?></div><div class="caption-fields"><strong><?=h($f)?></strong><?php if(in_array($ext,['jpg','jpeg','png','webp','gif','mp4','mov','m4v','webm'])): ?><input type="hidden" name="caption_files[]" value="<?=h($f)?>"><textarea name="captions[]" placeholder="Optional caption for this file"><?=h($editData['captions'][$f] ?? '')?></textarea><?php if(in_array($ext,['mp4','mov','m4v','webm'])): ?><label class="checkbox-label"><input type="checkbox" name="use_audio_files[]" value="<?=h($f)?>" <?=!empty($editData['video_audio'][$f])?'checked':''?>> <span>Use video file audio</span></label><?php endif; ?><?php else: ?><small>No caption for audio files</small><?php endif; ?></div><button type="button" onclick="deleteFile(<?=h(json_encode($f))?>)">Remove</button></div><?php endforeach; ?></div><button type="submit">Save changes</button></form><form method="post" id="delete-file-form" style="display:none"><input type="hidden" name="action" value="delete_file"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><input type="hidden" name="id" value="<?=h(ensure_article_id($editDir))?>"><input type="hidden" name="file" id="delete-file-name" value=""></form><form method="post" onsubmit="return confirm(\'Delete this input directory\?\')" id="delete-dir-form""><input type="hidden" name="action" value="delete_dir"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><input type="hidden" name="id" value="<?=h(ensure_article_id($editDir))?>"><label class="checkbox-label"><input type="checkbox" name="delete_output" value="1"> <span>Also delete generated video, if any</span></label><button class="danger" type="submit">Delete input dir</button></form></section><?php endif; ?>
|
||||
<?php if($tab==='new'): ?><section><h2>Add new movie input</h2><form method="post" enctype="multipart/form-data" id="new-form"><input type="hidden" name="action" value="create"><label>Title*<input name="title" required></label><label>Teaser<input name="teaser" maxlength="240"></label><label>Danish quote<input name="quote_da" maxlength="280"></label><label>Date<input name="date" placeholder="2026-05-25"></label><label>Place<input name="place"></label><label>Description<textarea name="description" maxlength="5000" data-counter="description-counter-new"></textarea><small id="description-counter-new" class="counter"></small></label><label>Images / videos*<input type="file" name="media[]" multiple required accept="image/*,video/*"></label><label>Audio (optional)<input type="file" name="audio[]" multiple accept="audio/*"></label><button type="submit">Create input-dir</button></form></section><?php endif; ?>
|
||||
<script>
|
||||
function deleteFile(name){
|
||||
if(!confirm('Remove this file?')) return;
|
||||
|
|
@ -1497,7 +1503,7 @@ document.addEventListener('DOMContentLoaded', function(){
|
|||
});
|
||||
}
|
||||
|
||||
function showModal(job, articleId, description, rawJson){
|
||||
function showModal(job, articleId, description, teaser, quoteDa, rawJson){
|
||||
var existing = document.getElementById('mvlog-gemini-modal');
|
||||
if (existing) existing.remove();
|
||||
var overlay = document.createElement('div');
|
||||
|
|
@ -1507,9 +1513,21 @@ document.addEventListener('DOMContentLoaded', function(){
|
|||
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');
|
||||
|
|
@ -1522,6 +1540,8 @@ document.addEventListener('DOMContentLoaded', function(){
|
|||
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);
|
||||
|
|
@ -1539,14 +1559,18 @@ document.addEventListener('DOMContentLoaded', function(){
|
|||
var textarea = editForm.querySelector('textarea[name="description"]');
|
||||
if (textarea){
|
||||
textarea.value = description || '';
|
||||
showToast('Inserted generated description into form. Click Save changes to apply.', true);
|
||||
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, description || ''); } catch(e){}
|
||||
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);
|
||||
|
|
@ -1573,7 +1597,10 @@ document.addEventListener('DOMContentLoaded', function(){
|
|||
.then(function(data){
|
||||
if (data && data.status === 'ok'){
|
||||
var desc = data.description || (data.raw_json && JSON.stringify(data.raw_json)) || '';
|
||||
showModal(job, articleId, desc, data.raw_json || null);
|
||||
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) {
|
||||
|
|
@ -1611,12 +1638,21 @@ document.addEventListener('DOMContentLoaded', function(){
|
|||
var articleId = idInput ? idInput.value : '';
|
||||
try {
|
||||
var key = 'mvlog_gemini_description_' + (articleId || job);
|
||||
var desc = localStorage.getItem(key);
|
||||
if (desc) {
|
||||
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;
|
||||
showToast('Inserted generated description into form. Click Save changes to apply.', true);
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue