Show on main landing page restore, separately from permalink
This commit is contained in:
parent
5505f0dee2
commit
edcb6ca928
4 changed files with 119 additions and 19 deletions
83
admin.php
83
admin.php
|
|
@ -6,6 +6,7 @@ foreach (["videos_dir", "thumbs_dir", "uploads_dir"] as $d) if (!is_dir($config[
|
|||
if (!is_dir(__DIR__ . "/cache")) mkdir(__DIR__ . "/cache", 0775, true);
|
||||
const ARTICLE_ID_FILE = '.mvlog-id';
|
||||
const MAP_HIDDEN_FILE = '.mvlog-hide-map';
|
||||
const PERMALINK_FILE = '.mvlog-permalink';
|
||||
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, "UTF-8"); }
|
||||
function ajax_json($data){ header('Content-Type: application/json; charset=UTF-8'); echo json_encode($data, JSON_UNESCAPED_UNICODE); exit; }
|
||||
function lower_text($s){ return function_exists('mb_strtolower') ? mb_strtolower((string)$s, 'UTF-8') : strtolower((string)$s); }
|
||||
|
|
@ -651,7 +652,7 @@ entries = []
|
|||
for dirpath, dirnames, filenames in os.walk(root):
|
||||
dirnames[:] = [d for d in dirnames if d != '.movmaker-lock']
|
||||
for name in filenames:
|
||||
if name in {'.movmaker-state.json', '.movmaker-enabled', '.movmaker-preview', '.mvlog-hidden', '.mvlog-id', '.mvlog-hide-map'}:
|
||||
if name in {'.movmaker-state.json', '.movmaker-enabled', '.movmaker-preview', '.mvlog-hidden', '.mvlog-permalink', '.mvlog-id', '.mvlog-hide-map'}:
|
||||
continue
|
||||
path = os.path.join(dirpath, name)
|
||||
rel = os.path.relpath(path, root)
|
||||
|
|
@ -763,6 +764,17 @@ function set_input_dir_visible($dir, $visible){
|
|||
if ($visible) { if (is_file($path)) unlink($path); }
|
||||
else { file_put_contents($path, "hidden\n"); @chmod($path, 0664); }
|
||||
}
|
||||
function input_dir_permalink($dir){ return is_file($dir . '/' . PERMALINK_FILE); }
|
||||
function input_dir_can_permalink($dir, $config){
|
||||
$state = input_dir_state($dir);
|
||||
$output = basename((string)($state['output'] ?? ''));
|
||||
return $output !== '' && is_file($config['videos_dir'] . '/' . $output) && !input_dir_preview($dir);
|
||||
}
|
||||
function set_input_dir_permalink($dir, $permalink){
|
||||
$path = $dir . '/' . PERMALINK_FILE;
|
||||
if ($permalink) { file_put_contents($path, "permalink\n"); @chmod($path, 0664); }
|
||||
elseif (is_file($path)) unlink($path);
|
||||
}
|
||||
function input_dir_map_hidden($dir){ return is_file($dir . '/' . MAP_HIDDEN_FILE); }
|
||||
function set_input_dir_map_hidden($dir, $hidden){
|
||||
$path = $dir . '/' . MAP_HIDDEN_FILE;
|
||||
|
|
@ -999,6 +1011,7 @@ try {
|
|||
'preview' => input_dir_preview($dir),
|
||||
'visible' => input_dir_visible($dir) && $canShow,
|
||||
'can_show' => $canShow,
|
||||
'can_permalink' => input_dir_can_permalink($dir, $config),
|
||||
'dir' => basename($dir),
|
||||
'id' => $articleId,
|
||||
'message' => $message,
|
||||
|
|
@ -1023,6 +1036,7 @@ try {
|
|||
'enabled' => input_dir_enabled($dir),
|
||||
'visible' => input_dir_visible($dir) && $canShow,
|
||||
'can_show' => $canShow,
|
||||
'can_permalink' => input_dir_can_permalink($dir, $config),
|
||||
'dir' => basename($dir),
|
||||
'id' => $articleId,
|
||||
'message' => $message,
|
||||
|
|
@ -1045,7 +1059,8 @@ try {
|
|||
include_once __DIR__ . '/lib/send_push.php';
|
||||
try_send_show_notification($articleId ?: basename($dir), $dir, __DIR__, '/var/log/mvlog_notify.log');
|
||||
} else {
|
||||
error_log("[mvlog] send_push helper missing, cannot send notification for " . basename($dir) . "\n", 3, '/var/log/mvlog_notify.log');
|
||||
error_log("[mvlog] send_push helper missing, cannot send notification for " . basename($dir) . "
|
||||
", 3, '/var/log/mvlog_notify.log');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1056,6 +1071,27 @@ try {
|
|||
'ok' => true,
|
||||
'visible' => $currentVisible,
|
||||
'can_show' => $canShow,
|
||||
'can_permalink' => input_dir_can_permalink($dir, $config),
|
||||
'dir' => basename($dir),
|
||||
'id' => $articleId,
|
||||
'message' => $message,
|
||||
]);
|
||||
}
|
||||
header('Location: admin.php?tab=edit&msg=' . rawurlencode($message));
|
||||
exit;
|
||||
} elseif ($action === 'set_permalink') {
|
||||
$dir = resolve_input_dir_from_request($config['uploads_dir'], $_POST['id'] ?? '', $_POST['dir'] ?? '', $articleIndex);
|
||||
$articleId = ensure_state_article_id($dir);
|
||||
$permalinkNow = !empty($_POST['permalink']);
|
||||
$canPermalink = input_dir_can_permalink($dir, $config);
|
||||
if ($permalinkNow && !$canPermalink) throw new RuntimeException('Only finished full-quality videos can use permalinks.');
|
||||
set_input_dir_permalink($dir, $permalinkNow && $canPermalink);
|
||||
$message = ($permalinkNow ? 'Permalink enabled for: in-dir/' : 'Permalink disabled for: in-dir/') . basename($dir);
|
||||
if (!empty($_POST['ajax'])) {
|
||||
ajax_json([
|
||||
'ok' => true,
|
||||
'permalink' => $permalinkNow && $canPermalink,
|
||||
'can_permalink' => $canPermalink,
|
||||
'dir' => basename($dir),
|
||||
'id' => $articleId,
|
||||
'message' => $message,
|
||||
|
|
@ -1313,7 +1349,7 @@ $runningJobs = active_worker_jobs($config);
|
|||
<?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/teaser/quote_da/description or use date=... / location=..." aria-label="Search input dirs"><a class="admin-search-clear <?=$searchEmpty ? 'is-empty' : ''?>" href="admin.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 : ''; $posterUrl=$hasVideo ? video_thumb_public_url($config, $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 $map_n = ($displayOutput !== '' ? pathinfo($displayOutput, PATHINFO_FILENAME) : basename($d)) . '_map.png'; $hasMap = is_file($config['videos_dir'].'/'.$map_n); ?><?php if($hasVideo): ?><div class="video-wrapper"><video controls preload="metadata" src="<?=h($config['public_videos'].'/'.rawurlencode($displayOutput))?>"<?= $posterUrl !== '' ? ' poster="'.h($posterUrl).'"' : '' ?>></video><?php if($hasMap): ?><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 if($hasMap): ?><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; ?><?php endif; ?></div><div class="video-info"><?php $thumbMeta = $hasVideo ? video_thumb_source_info($config, $displayOutput) : []; if(!empty($thumbMeta['source_file'])): ?><p style="margin:.2rem 0 .45rem;color:#A0A4AB;font-size:.82rem">Thumb source: <?=h((string)$thumbMeta['source_file'])?></p><?php endif; ?><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="admin.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 : ''; $posterUrl=$hasVideo ? video_thumb_public_url($config, $displayOutput) : ''; $previewVideo=$hasVideo && $displayOutput === $previewOutput; $canShow=$hasFullVideo && !$preview; $visible=$canShow && input_dir_visible($d); $mapHidden=input_dir_map_hidden($d); $permalink=input_dir_permalink($d); $canPermalink=input_dir_can_permalink($d, $config); $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))?>" data-permalink="<?= $permalink ? '1' : '0' ?>" data-can-permalink="<?= $canPermalink ? '1' : '0' ?>"><div><?php $map_n = ($displayOutput !== '' ? pathinfo($displayOutput, PATHINFO_FILENAME) : basename($d)) . '_map.png'; $hasMap = is_file($config['videos_dir'].'/'.$map_n); ?><?php if($hasVideo): ?><div class="video-wrapper"><video controls preload="metadata" src="<?=h($config['public_videos'].'/'.rawurlencode($displayOutput))?>"<?= $posterUrl !== '' ? ' poster="'.h($posterUrl).'"' : '' ?>></video><?php if($hasMap): ?><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 if($hasMap): ?><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; ?><?php endif; ?></div><div class="video-info"><?php $thumbMeta = $hasVideo ? video_thumb_source_info($config, $displayOutput) : []; if(!empty($thumbMeta['source_file'])): ?><p style="margin:.2rem 0 .45rem;color:#A0A4AB;font-size:.82rem">Thumb source: <?=h((string)$thumbMeta['source_file'])?></p><?php endif; ?><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))?>">
|
||||
|
|
@ -1344,6 +1380,16 @@ $runningJobs = active_worker_jobs($config);
|
|||
</label>
|
||||
<noscript><button type="submit">Apply</button></noscript>
|
||||
</form>
|
||||
<form method="post" class="inline-form switch-form" data-switch="permalink">
|
||||
<input type="hidden" name="action" value="set_permalink">
|
||||
<input type="hidden" name="dir" value="<?=h(basename($d))?>">
|
||||
<input type="hidden" name="id" value="<?=h(read_article_id($d))?>">
|
||||
<label class="switch-label">
|
||||
<input type="checkbox" name="permalink" value="1" <?=$permalink?'checked':''?> <?=!$canPermalink?'disabled':''?>>
|
||||
<span class="switch-ui" aria-hidden="true"></span><span class="switch-text">Permalink</span>
|
||||
</label>
|
||||
<noscript><button type="submit">Apply</button></noscript>
|
||||
</form>
|
||||
<form method="post" class="inline-form switch-form" data-switch="hide-map">
|
||||
<input type="hidden" name="action" value="set_map_hidden">
|
||||
<input type="hidden" name="dir" value="<?=h(basename($d))?>">
|
||||
|
|
@ -1355,7 +1401,7 @@ $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><?php $articleIdForLink = read_article_id($d); $titleText = h($meta['title'] ?: ($info['title'] ?? basename($d))); ?><h2><?php if(article_id_is_valid($articleIdForLink)): ?><a href="index.php?id=<?=rawurlencode($articleIdForLink)?>"><?= $titleText ?></a><?php else: ?><?= $titleText ?><?php endif; ?></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; ?>
|
||||
</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><?php $articleIdForLink = read_article_id($d); $titlePlain = $meta['title'] ?: ($info['title'] ?? basename($d)); $titleText = h($titlePlain); ?><h2 class="video-row-title" data-title-text="<?=h($titlePlain)?>" data-permalink-url="index.php?id=<?=rawurlencode($articleIdForLink)?>"><?php if(article_id_is_valid($articleIdForLink)): ?><a href="index.php?id=<?=rawurlencode($articleIdForLink)?>" target="_blank"><?= $titleText ?></a><?php else: ?><?= $titleText ?><?php endif; ?></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>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><div class="file-actions" style="display:flex;gap:.45rem;flex-wrap:wrap;align-self:start"><?php if(in_array($ext,['jpg','jpeg','png','webp','gif'])): ?><button type="button" class="set-thumb-btn" onclick="setThumb(<?=h(json_encode($f))?>)">Set as thumb</button><?php endif; ?><?php if(in_array($ext,['jpg','jpeg','png','webp','gif','mp4','mov','m4v','webm'])): ?><button type="button" class="button order-btn-up" title="Move up" onclick="moveFileOrder(<?=h(json_encode($f))?>,'up',this.closest('.caption-row'),this)">↑</button><button type="button" class="button order-btn-down" title="Move down" onclick="moveFileOrder(<?=h(json_encode($f))?>,'down',this.closest('.caption-row'),this)">↓</button><?php endif; ?><button type="button" onclick="deleteFile(<?=h(json_encode($f))?>)">Remove</button></div></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 Article</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</button></form></section><?php endif; ?>
|
||||
|
|
@ -1775,6 +1821,23 @@ async function handleSwitchToggle(form, checkbox){
|
|||
}
|
||||
}
|
||||
|
||||
function updateAdminRowTitleLink(row){
|
||||
if(!row) return;
|
||||
const titleEl = row.querySelector('h2');
|
||||
if(!titleEl) return;
|
||||
const title = titleEl.dataset.titleText || titleEl.textContent.trim();
|
||||
const url = titleEl.dataset.permalinkUrl || '';
|
||||
titleEl.textContent = '';
|
||||
if(url){
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.textContent = title;
|
||||
titleEl.appendChild(link);
|
||||
} else {
|
||||
titleEl.textContent = title;
|
||||
}
|
||||
}
|
||||
|
||||
function applySwitchPayload(payload){
|
||||
if(!payload) return;
|
||||
let row = null;
|
||||
|
|
@ -1790,6 +1853,10 @@ function applySwitchPayload(payload){
|
|||
if('preview' in payload) setSwitch('preview', payload.preview);
|
||||
if('enabled' in payload) setSwitch('render', payload.enabled);
|
||||
if('map_hidden' in payload) setSwitch('hide-map', payload.map_hidden);
|
||||
if('permalink' in payload){
|
||||
setSwitch('permalink', payload.permalink);
|
||||
row.dataset.permalink = payload.permalink ? '1' : '0';
|
||||
}
|
||||
if('visible' in payload){
|
||||
setSwitch('show', payload.visible);
|
||||
row.classList.toggle('shown', !!payload.visible);
|
||||
|
|
@ -1805,6 +1872,14 @@ function applySwitchPayload(payload){
|
|||
}
|
||||
}
|
||||
}
|
||||
if('can_permalink' in payload){
|
||||
row.dataset.canPermalink = payload.can_permalink ? '1' : '0';
|
||||
const permalinkInput = row.querySelector('form[data-switch="permalink"] input[type="checkbox"]');
|
||||
if(permalinkInput){
|
||||
permalinkInput.disabled = !payload.can_permalink;
|
||||
}
|
||||
}
|
||||
updateAdminRowTitleLink(row);
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
|
|
|
|||
51
index.php
51
index.php
|
|
@ -104,6 +104,27 @@ function video_article_id($config, $videoName){
|
|||
}
|
||||
return '';
|
||||
}
|
||||
function input_dirs($base){ return glob($base.'/*', GLOB_ONLYDIR) ?: []; }
|
||||
function read_article_id($dir){
|
||||
$idFile = rtrim((string)$dir, '/').'/.mvlog-id';
|
||||
if (!is_file($idFile)) return '';
|
||||
$id = trim((string)file_get_contents($idFile));
|
||||
return preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $id) ? $id : '';
|
||||
}
|
||||
function input_dir_permalink($dir){ return is_file($dir . '/.mvlog-permalink'); }
|
||||
function article_permalink_enabled($config, $articleId){
|
||||
static $cache = null;
|
||||
if ($cache === null) {
|
||||
$cache = [];
|
||||
foreach (input_dirs($config['uploads_dir']) as $dir) {
|
||||
$id = read_article_id($dir);
|
||||
if ($id !== '') $cache[$id] = input_dir_permalink($dir);
|
||||
}
|
||||
}
|
||||
$articleId = strtolower(trim((string)$articleId));
|
||||
if (!preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $articleId)) return false;
|
||||
return !empty($cache[$articleId]);
|
||||
}
|
||||
function article_og_image_url($articleId){
|
||||
$articleId = strtolower(trim((string)$articleId));
|
||||
if (!preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $articleId)) return '';
|
||||
|
|
@ -252,14 +273,14 @@ function load_video_cache($videos){
|
|||
|
||||
$hiddenOutputs = hidden_video_outputs($config);
|
||||
$hiddenMapOutputs = map_hidden_outputs($config);
|
||||
$videos = array_values(array_filter(
|
||||
$allVideos = array_values(array_filter(
|
||||
glob($config['videos_dir'].'/*.{mp4,webm,mov,m4v}', GLOB_BRACE) ?: [],
|
||||
fn($v)=>empty($hiddenOutputs[basename($v)]) && !preg_match('/_preview\.(mp4|webm|mov|m4v)$/i', basename($v))
|
||||
fn($v)=>!preg_match('/_preview\.(mp4|webm|mov|m4v)$/i', basename($v))
|
||||
));
|
||||
|
||||
$videoCache = load_video_cache($videos);
|
||||
$videoCache = load_video_cache($allVideos);
|
||||
|
||||
usort($videos, function($a, $b) use ($videoCache) {
|
||||
usort($allVideos, function($a, $b) use ($videoCache) {
|
||||
$am = $videoCache[basename($a)] ?? [];
|
||||
$bm = $videoCache[basename($b)] ?? [];
|
||||
$ad = strtotime((string)($am['metadata']['sort_date'] ?? $am['metadata']['date'] ?? '')) ?: filemtime($a);
|
||||
|
|
@ -271,18 +292,18 @@ $articleId = trim((string)($_GET['id'] ?? ''));
|
|||
if ($articleId !== '' && (!preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $articleId) || count(array_diff_key($_GET, ['id' => true])) > 0)) {
|
||||
render_404_page();
|
||||
}
|
||||
if ($articleId === '') {
|
||||
render_blank_page();
|
||||
}
|
||||
|
||||
if ($articleId !== '') {
|
||||
$videos = array_values(array_filter($videos, function($v) use ($articleId, $config) {
|
||||
if (!article_permalink_enabled($config, $articleId)) {
|
||||
render_404_page();
|
||||
}
|
||||
$videos = array_values(array_filter($allVideos, function($v) use ($articleId, $config) {
|
||||
return video_article_id($config, basename($v)) === $articleId;
|
||||
}));
|
||||
$rawKeyword = '';
|
||||
$keywordType = 'general';
|
||||
$keywordValue = '';
|
||||
} else {
|
||||
$videos = array_values(array_filter($allVideos, fn($v) => empty($hiddenOutputs[basename($v)])));
|
||||
$rawKeyword = trim((string)($_GET['q'] ?? ''));
|
||||
|
||||
// Backward compatibility with older filter/query formats.
|
||||
|
|
@ -341,6 +362,9 @@ $pages = max(1, (int)ceil($total / $per));
|
|||
$page = min($page, $pages);
|
||||
$slice = array_slice($videos, ($page - 1) * $per, $per);
|
||||
if (!$slice) {
|
||||
if ($articleId === '') {
|
||||
render_blank_page();
|
||||
}
|
||||
render_404_page();
|
||||
}
|
||||
|
||||
|
|
@ -367,7 +391,6 @@ if ($articleId !== '') {
|
|||
}
|
||||
$headerTitles = require __DIR__ . '/header_titles.php';
|
||||
$siteHeaderTitle = $headerTitles[array_rand($headerTitles)];
|
||||
$brandUrl = '?id=' . rawurlencode($articleId);
|
||||
?>
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
|
@ -409,8 +432,9 @@ $brandUrl = '?id=' . rawurlencode($articleId);
|
|||
.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}
|
||||
.filter-summary{color:#A0A4AB;font-size:.9rem;margin:0 0 .8rem}
|
||||
.video-row-title{font-size:1.35rem;line-height:1.2;color:#F3F4F6}
|
||||
.video-row-title a{color:inherit;text-decoration:none}
|
||||
.video-row-title a:hover{text-decoration:underline}
|
||||
.video-row-title a{color:inherit;text-decoration:none;cursor:pointer}
|
||||
.video-row-title a:visited{color:inherit}
|
||||
.video-row-title a:hover,.video-row-title a:focus,.video-row-title a:active{text-decoration:none}
|
||||
.video-row-head-mobile{display:none}
|
||||
.video-row-head-mobile .video-row-title{margin:0 0 .15rem}
|
||||
.video-row-head-mobile .meta{margin:0 0 .25rem}
|
||||
|
|
@ -469,7 +493,8 @@ $brandUrl = '?id=' . rawurlencode($articleId);
|
|||
$name = basename($v);
|
||||
$base = pathinfo($name, PATHINFO_FILENAME);
|
||||
$articleIdForVideo = video_article_id($config, $name);
|
||||
$permalinkUrl = $articleIdForVideo !== '' ? query_url(['id' => $articleIdForVideo]) : '';
|
||||
$permalinkEnabled = $articleIdForVideo !== '' && article_permalink_enabled($config, $articleIdForVideo);
|
||||
$permalinkUrl = $permalinkEnabled ? query_url(['id' => $articleIdForVideo]) : '';
|
||||
$url = $config['public_videos'].'/'.rawurlencode($name);
|
||||
$m = $videoCache[$name]['metadata'] ?? video_metadata($v);
|
||||
$posterUrl = video_thumb_public_url($config, $name);
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ function try_send_show_notification($articleRefOrJob, $jobdir, $mvlog_root = nul
|
|||
|
||||
if ($articleTitle === '') $articleTitle = mvlog_nice_title((string)$job);
|
||||
|
||||
$articleUrl = './?id=' . rawurlencode($articleId);
|
||||
$articleUrl = is_file($jobdir . '/.mvlog-permalink') ? './?id=' . rawurlencode($articleId) : './';
|
||||
|
||||
$payload = json_encode([
|
||||
'title' => $articleTitle,
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue