intégration des contenus prémaquettes
This commit is contained in:
parent
5afef4d2d4
commit
5e4fab99c4
105 changed files with 616 additions and 441 deletions
|
|
@ -11,33 +11,6 @@ fields:
|
|||
back: black
|
||||
uploads:
|
||||
template: blocks/image
|
||||
imagered:
|
||||
label: Image (couche rouge)
|
||||
type: files
|
||||
query: model.images
|
||||
multiple: false
|
||||
image:
|
||||
back: black
|
||||
uploads:
|
||||
template: blocks/image
|
||||
imagegreen:
|
||||
label: Image (couche verte)
|
||||
type: files
|
||||
query: model.images
|
||||
multiple: false
|
||||
image:
|
||||
back: black
|
||||
uploads:
|
||||
template: blocks/image
|
||||
imageblue:
|
||||
label: Image (couche bleu)
|
||||
type: files
|
||||
query: model.images
|
||||
multiple: false
|
||||
image:
|
||||
back: black
|
||||
uploads:
|
||||
template: blocks/image
|
||||
alt:
|
||||
label: field.blocks.image.alt
|
||||
type: text
|
||||
|
|
|
|||
|
|
@ -11,33 +11,6 @@ fields:
|
|||
back: black
|
||||
uploads:
|
||||
template: blocks/image
|
||||
imagered:
|
||||
label: Image (couche rouge)
|
||||
type: files
|
||||
query: model.images
|
||||
multiple: false
|
||||
image:
|
||||
back: black
|
||||
uploads:
|
||||
template: blocks/image
|
||||
imagegreen:
|
||||
label: Image (couche verte)
|
||||
type: files
|
||||
query: model.images
|
||||
multiple: false
|
||||
image:
|
||||
back: black
|
||||
uploads:
|
||||
template: blocks/image
|
||||
imageblue:
|
||||
label: Image (couche bleu)
|
||||
type: files
|
||||
query: model.images
|
||||
multiple: false
|
||||
image:
|
||||
back: black
|
||||
uploads:
|
||||
template: blocks/image
|
||||
alt:
|
||||
label: field.blocks.image.alt
|
||||
type: text
|
||||
|
|
|
|||
56
site/plugins/image-usage-indicator/index.css
Normal file
56
site/plugins/image-usage-indicator/index.css
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/* ─── Image Usage Indicator ───────────────────────────────────────────────── */
|
||||
|
||||
/* Highlight used images with a subtle green border */
|
||||
.k-item.sf-is-used {
|
||||
border-right: 3px solid #3cba6f;
|
||||
}
|
||||
|
||||
.k-item.sf-is-unused {
|
||||
border-right: 3px solid #e8921e;
|
||||
}
|
||||
|
||||
/* Ensure the image/icon container can hold the absolute badge */
|
||||
.k-item .k-item-image,
|
||||
.k-item .k-item-icon {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Badge dot common styles */
|
||||
.sf-usage-dot {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
/*border: 1px solid rgba(255, 255, 255, 0.85);*/
|
||||
font-size: 9px;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
/* Used: green with a checkmark */
|
||||
.sf-usage-dot--used {
|
||||
background-color: #3cba6f;
|
||||
color: #fff;
|
||||
}
|
||||
.sf-usage-dot--used::before {
|
||||
/*content: '✓';*/
|
||||
}
|
||||
|
||||
/* Unused: orange with an em-dash */
|
||||
.sf-usage-dot--unused {
|
||||
background-color: #e8921e;
|
||||
color: #fff;
|
||||
}
|
||||
.sf-usage-dot--unused::before {
|
||||
/*content: '–';*/
|
||||
font-size: 11px;
|
||||
}
|
||||
147
site/plugins/image-usage-indicator/index.js
Normal file
147
site/plugins/image-usage-indicator/index.js
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Set to true to see debug output in the browser console
|
||||
var DEBUG = false;
|
||||
|
||||
var usageCache = {};
|
||||
var pendingRequests = {};
|
||||
var debounceTimer = null;
|
||||
|
||||
function log() {
|
||||
if (DEBUG) console.log.apply(console, ['[image-usage]'].concat(Array.prototype.slice.call(arguments)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch usage data for a Kirby page ID (slug format, e.g. "community-led/inside").
|
||||
* Returns a promise resolving to { filename: boolean }.
|
||||
*/
|
||||
function fetchUsage(pageId) {
|
||||
if (pageId in usageCache) return Promise.resolve(usageCache[pageId]);
|
||||
if (pageId in pendingRequests) return pendingRequests[pageId];
|
||||
|
||||
// Kirby panel encodes / as + in URLs
|
||||
var encoded = encodeURIComponent(pageId.replace(/\//g, '+'));
|
||||
log('Fetching usage for page:', pageId, '→', '/api/smart-forests/image-usage/' + encoded);
|
||||
|
||||
var csrf = (window.panel && window.panel.system && window.panel.system.csrf) ? window.panel.system.csrf : false;
|
||||
|
||||
var promise = fetch('/api/smart-forests/image-usage/' + encoded, {
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'x-csrf': csrf,
|
||||
'x-fiber': 'true'
|
||||
}
|
||||
})
|
||||
.then(function (r) {
|
||||
if (!r.ok) {
|
||||
log('API error', r.status, r.statusText);
|
||||
return {};
|
||||
}
|
||||
return r.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
log('Usage data for', pageId, data);
|
||||
usageCache[pageId] = data;
|
||||
delete pendingRequests[pageId];
|
||||
return data;
|
||||
})
|
||||
.catch(function (err) {
|
||||
log('Fetch failed:', err);
|
||||
usageCache[pageId] = {};
|
||||
delete pendingRequests[pageId];
|
||||
return {};
|
||||
});
|
||||
|
||||
pendingRequests[pageId] = promise;
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply (or refresh) a usage badge on a file item element.
|
||||
* Skips DOM writes if the element is already in the correct state,
|
||||
* which prevents the MutationObserver from re-triggering indefinitely.
|
||||
*/
|
||||
function applyBadge(item, used) {
|
||||
var wantedState = used === true ? 'used' : used === false ? 'unused' : null;
|
||||
var currentState = item.dataset.sfUsage || null;
|
||||
|
||||
// Already correct — skip to avoid triggering MutationObserver
|
||||
if (currentState === wantedState) return;
|
||||
|
||||
// Remove existing badge
|
||||
item.querySelectorAll('.sf-usage-dot').forEach(function (el) { el.remove(); });
|
||||
item.classList.remove('sf-is-used', 'sf-is-unused');
|
||||
delete item.dataset.sfUsage;
|
||||
|
||||
if (!wantedState) return;
|
||||
|
||||
item.dataset.sfUsage = wantedState;
|
||||
item.classList.add('sf-is-' + wantedState);
|
||||
|
||||
var dot = document.createElement('span');
|
||||
dot.className = 'sf-usage-dot sf-usage-dot--' + wantedState;
|
||||
dot.setAttribute('title', used ? 'Utilisée dans le contenu' : 'Non utilisée dans le contenu');
|
||||
|
||||
var imageArea = item.querySelector('.k-item-image, .k-item-icon');
|
||||
if (imageArea) {
|
||||
imageArea.appendChild(dot);
|
||||
} else {
|
||||
item.prepend(dot);
|
||||
}
|
||||
}
|
||||
|
||||
var IMAGE_EXT = /\.(jpe?g|png|gif|webp|svg|avif)$/i;
|
||||
|
||||
/**
|
||||
* Scan current DOM for file items, group by page, fetch usage, paint badges.
|
||||
*
|
||||
* File items in the panel have data-id = "{kirby-page-slug}/{filename}"
|
||||
* e.g. "community-led-forest-technologies/inside/image.jpg"
|
||||
*/
|
||||
function run() {
|
||||
// All k-item elements that have a data-id ending in an image extension
|
||||
var allItems = document.querySelectorAll('.k-item[data-id]');
|
||||
log('Total .k-item[data-id] found:', allItems.length);
|
||||
|
||||
var byPage = {};
|
||||
allItems.forEach(function (item) {
|
||||
var dataId = item.getAttribute('data-id') || '';
|
||||
if (!IMAGE_EXT.test(dataId)) return; // skip non-image items
|
||||
|
||||
var parts = dataId.split('/');
|
||||
if (parts.length < 2) return;
|
||||
|
||||
var filename = parts[parts.length - 1];
|
||||
var pageId = parts.slice(0, -1).join('/');
|
||||
|
||||
if (!byPage[pageId]) byPage[pageId] = [];
|
||||
byPage[pageId].push({ item: item, filename: filename });
|
||||
});
|
||||
|
||||
var pageCount = Object.keys(byPage).length;
|
||||
log('Pages with image items:', pageCount, byPage);
|
||||
|
||||
if (!pageCount) return;
|
||||
|
||||
Object.keys(byPage).forEach(function (pageId) {
|
||||
fetchUsage(pageId).then(function (usage) {
|
||||
byPage[pageId].forEach(function (entry) {
|
||||
applyBadge(entry.item, usage[entry.filename]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function schedule() {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(run, 450);
|
||||
}
|
||||
|
||||
new MutationObserver(schedule).observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
schedule();
|
||||
})();
|
||||
82
site/plugins/image-usage-indicator/index.php
Normal file
82
site/plugins/image-usage-indicator/index.php
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Plugin: image-usage-indicator
|
||||
*
|
||||
* Exposes an API route that scans all content files for file:// UUID references,
|
||||
* and returns which images (by filename) are actually used on a given page.
|
||||
*
|
||||
* Used by index.js to add visual badges in the panel files section.
|
||||
*/
|
||||
|
||||
Kirby::plugin('smart-forests/image-usage-indicator', [
|
||||
'api' => [
|
||||
'routes' => [
|
||||
[
|
||||
'pattern' => 'smart-forests/image-usage/(:any)',
|
||||
'method' => 'GET',
|
||||
'action' => function (string $encodedId) {
|
||||
$kirby = kirby();
|
||||
|
||||
// Panel encodes page IDs with + instead of /
|
||||
$pageId = str_replace('+', '/', rawurldecode($encodedId));
|
||||
$page = $kirby->page($pageId);
|
||||
|
||||
if (!$page) {
|
||||
return ['error' => 'Page not found: ' . $pageId];
|
||||
}
|
||||
|
||||
// Build UUID → filename map for all files on this page
|
||||
$uuidMap = [];
|
||||
foreach ($page->files() as $file) {
|
||||
$uuid = $file->content()->get('uuid')->value();
|
||||
if ($uuid) {
|
||||
$uuidMap[$uuid] = $file->filename();
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($uuidMap)) {
|
||||
return (object)[];
|
||||
}
|
||||
|
||||
// Scan all content .txt files once for file:// UUID references
|
||||
$usedUuids = [];
|
||||
$contentRoot = $kirby->root('content');
|
||||
$iter = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator(
|
||||
$contentRoot,
|
||||
RecursiveDirectoryIterator::SKIP_DOTS
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($iter as $fsFile) {
|
||||
// Stop early when all UUIDs are accounted for
|
||||
if (count($usedUuids) === count($uuidMap)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($fsFile->getExtension() !== 'txt') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = file_get_contents($fsFile->getPathname());
|
||||
|
||||
foreach ($uuidMap as $uuid => $_) {
|
||||
if (!isset($usedUuids[$uuid]) && strpos($content, 'file://' . $uuid) !== false) {
|
||||
$usedUuids[$uuid] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return filename => isUsed boolean
|
||||
$result = [];
|
||||
foreach ($uuidMap as $uuid => $filename) {
|
||||
$result[$filename] = isset($usedUuids[$uuid]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
Loading…
Add table
Add a link
Reference in a new issue