Use thumbnail/placeholder logic for imagebox
This commit is contained in:
@@ -35,3 +35,7 @@ $conf['gallery_thumb_scale'] = 1;
|
||||
|
||||
// Local client service used by {{open>...}}.
|
||||
$conf['open_service_url'] = 'http://127.0.0.1:8765';
|
||||
|
||||
// Image syntax defaults
|
||||
$conf['default_image_width'] = 250;
|
||||
$conf['default_image_align'] = 'right'; // left|right|center
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
}
|
||||
|
||||
function init() {
|
||||
var imgs = document.querySelectorAll('div.luxtools-gallery img[data-thumb-src]');
|
||||
// Handle both gallery and imagebox thumbnails
|
||||
var imgs = document.querySelectorAll('div.luxtools-gallery img[data-thumb-src], div.luxtools-imagebox img[data-thumb-src]');
|
||||
if (!imgs || !imgs.length) return;
|
||||
|
||||
if ('IntersectionObserver' in window) {
|
||||
|
||||
@@ -74,41 +74,58 @@ class Output
|
||||
$genThumbW = (int)max(1, (int)round($thumbW * $thumbScale));
|
||||
$genThumbH = (int)max(1, (int)round($thumbH * $thumbScale));
|
||||
|
||||
$placeholderUrl = DOKU_BASE . 'lib/images/blank.gif';
|
||||
$placeholderId = $syntax ? trim((string)$syntax->getConf('thumb_placeholder')) : '';
|
||||
if ($placeholderId !== '' && function_exists('ml')) {
|
||||
// ml() builds a fetch.php URL for a MediaManager item
|
||||
$placeholderUrl = ml($placeholderId, ['w' => $genThumbW, 'h' => $genThumbH], true, '&');
|
||||
}
|
||||
|
||||
/** @var \Doku_Renderer_xhtml $renderer */
|
||||
$renderer = $this->renderer;
|
||||
$renderer->doc .= '<div class="luxtools-plugin luxtools-gallery" data-luxtools-gallery="1">';
|
||||
|
||||
global $ID;
|
||||
$pageId = isset($ID) ? (string)$ID : '';
|
||||
if (function_exists('cleanID')) {
|
||||
$pageId = (string)cleanID($pageId);
|
||||
}
|
||||
|
||||
foreach ($this->files as $item) {
|
||||
$url = $this->itemWebUrl($item, !empty($params['randlinks']));
|
||||
$thumbUrl = $this->withQueryParams($url, [
|
||||
'thumb' => 1,
|
||||
'w' => $genThumbW,
|
||||
'h' => $genThumbH,
|
||||
// Keep quality explicit so cache file naming stays stable.
|
||||
'q' => $thumbQ,
|
||||
]);
|
||||
$safeUrl = hsc($url);
|
||||
$safeThumbUrl = hsc($thumbUrl);
|
||||
$safePlaceholderUrl = hsc($placeholderUrl);
|
||||
$label = hsc($item['name']);
|
||||
$caption = hsc(basename((string)($item['name'] ?? '')));
|
||||
if ($caption === '') {
|
||||
$caption = $label;
|
||||
}
|
||||
|
||||
$initialSrc = $safePlaceholderUrl;
|
||||
$dataThumb = ' data-thumb-src="' . $safeThumbUrl . '"';
|
||||
$thumbCachePath = $this->thumbCachePathForItem($item, $genThumbW, $genThumbH, $thumbQ);
|
||||
if (is_string($thumbCachePath) && $thumbCachePath !== '' && @is_file($thumbCachePath)) {
|
||||
// Thumb already exists: start with it immediately (no JS swap needed)
|
||||
$initialSrc = $safeThumbUrl;
|
||||
// Use ThumbnailHelper to get thumbnail info
|
||||
$imagePath = isset($item['path']) && is_string($item['path']) ? $item['path'] : '';
|
||||
if ($imagePath !== '' && is_file($imagePath)) {
|
||||
// Extract root and local from full path for helper
|
||||
$root = $this->basedir;
|
||||
$local = '';
|
||||
if (str_starts_with($imagePath, $root)) {
|
||||
$local = substr($imagePath, strlen($root));
|
||||
}
|
||||
|
||||
$thumb = ThumbnailHelper::getThumbnail(
|
||||
$root,
|
||||
$local,
|
||||
$pageId,
|
||||
$genThumbW,
|
||||
$genThumbH,
|
||||
$thumbQ,
|
||||
$placeholderId !== '' ? $placeholderId : null
|
||||
);
|
||||
|
||||
$initialSrc = hsc($thumb['url']);
|
||||
$dataThumb = $thumb['isFinal'] ? '' : ' data-thumb-src="' . hsc($thumb['thumbUrl']) . '"';
|
||||
} else {
|
||||
// Fallback: use URL directly
|
||||
$thumbUrl = $this->withQueryParams($url, [
|
||||
'thumb' => 1,
|
||||
'w' => $genThumbW,
|
||||
'h' => $genThumbH,
|
||||
'q' => $thumbQ,
|
||||
]);
|
||||
$initialSrc = hsc($thumbUrl);
|
||||
$dataThumb = '';
|
||||
}
|
||||
|
||||
@@ -161,44 +178,6 @@ class Output
|
||||
return $url . $glue . http_build_query($params, '', '&', PHP_QUERY_RFC3986) . $fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the expected thumbnail cache path for an item.
|
||||
*
|
||||
* Mirrors the hashing scheme in file.php so we can detect whether a thumb
|
||||
* already exists and can be used immediately.
|
||||
*
|
||||
* @param array $item
|
||||
* @param int $w
|
||||
* @param int $h
|
||||
* @param int $q
|
||||
* @return string|null
|
||||
*/
|
||||
protected function thumbCachePathForItem(array $item, int $w, int $h, int $q): ?string
|
||||
{
|
||||
if (!isset($item['path']) || !is_string($item['path']) || $item['path'] === '') return null;
|
||||
if (!isset($item['mtime'])) return null;
|
||||
|
||||
$path = $item['path'];
|
||||
$mtime = (int)$item['mtime'];
|
||||
|
||||
// Decide output format the same way file.php does.
|
||||
try {
|
||||
[, $mime,] = mimetype($path, false);
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
if (!is_string($mime) || !str_starts_with($mime, 'image/')) return null;
|
||||
$dstFormat = ($mime === 'image/png' || $mime === 'image/gif') ? 'png' : 'jpg';
|
||||
|
||||
global $conf;
|
||||
if (!isset($conf['cachedir']) || !is_string($conf['cachedir']) || trim($conf['cachedir']) === '') return null;
|
||||
|
||||
$hash = sha1($path . '|' . $mtime . '|w=' . $w . '|h=' . $h . '|q=' . $q . '|f=' . $dstFormat);
|
||||
$sub = substr($hash, 0, 2);
|
||||
$cacheDir = rtrim($conf['cachedir'], '/');
|
||||
return $cacheDir . '/luxtools/thumbs/' . $sub . '/' . $hash . '.' . $dstFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the files as a table, including details if configured that way.
|
||||
*
|
||||
|
||||
189
src/ThumbnailHelper.php
Normal file
189
src/ThumbnailHelper.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\plugin\luxtools;
|
||||
|
||||
/**
|
||||
* Shared utility for thumbnail cache management and display logic.
|
||||
*
|
||||
* Used by both the image syntax and the gallery rendering to avoid code duplication.
|
||||
*/
|
||||
class ThumbnailHelper
|
||||
{
|
||||
/**
|
||||
* Get thumbnail URL and metadata for rendering.
|
||||
*
|
||||
* This is the main entry point for getting thumbnails. The helper handles
|
||||
* all URL construction, cache checking, and provides ready-to-use URLs.
|
||||
*
|
||||
* @param string $rootPath Root filesystem path (e.g., '/data/images/')
|
||||
* @param string $localPath Local path relative to root (e.g., 'photo.jpg')
|
||||
* @param string $pageId Page ID for ACL check
|
||||
* @param int $width Desired width
|
||||
* @param int $height Desired height
|
||||
* @param int $quality JPEG quality (0-100)
|
||||
* @param string|null $placeholderId Optional MediaManager ID for custom placeholder
|
||||
* @return array [
|
||||
* 'url' => string, // Always usable URL (thumbnail or placeholder)
|
||||
* 'isFinal' => bool, // true if thumbnail ready, false if placeholder
|
||||
* 'thumbUrl' => string // Real thumbnail URL (for lazy loading)
|
||||
* ]
|
||||
*/
|
||||
public static function getThumbnail(
|
||||
string $rootPath,
|
||||
string $localPath,
|
||||
string $pageId,
|
||||
int $width,
|
||||
int $height,
|
||||
int $quality = 80,
|
||||
?string $placeholderId = null
|
||||
): array {
|
||||
$fullPath = $rootPath . $localPath;
|
||||
$thumbUrl = self::buildThumbnailUrl($rootPath, $localPath, $pageId, $width, $height, $quality);
|
||||
|
||||
// Check if cached
|
||||
$cachePath = self::getCachePath($fullPath, $width, $height, $quality);
|
||||
$isCached = $cachePath !== null && @is_file($cachePath);
|
||||
|
||||
if ($isCached) {
|
||||
return [
|
||||
'url' => $thumbUrl,
|
||||
'isFinal' => true,
|
||||
'thumbUrl' => $thumbUrl,
|
||||
];
|
||||
}
|
||||
|
||||
// Not cached: return placeholder
|
||||
return [
|
||||
'url' => self::getPlaceholderUrl($width, $height, $placeholderId),
|
||||
'isFinal' => false,
|
||||
'thumbUrl' => $thumbUrl,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the file.php URL for a thumbnail.
|
||||
*
|
||||
* @param string $rootPath Root filesystem path
|
||||
* @param string $localPath Local path relative to root
|
||||
* @param string $pageId Page ID for ACL check
|
||||
* @param int $width Width
|
||||
* @param int $height Height
|
||||
* @param int $quality JPEG quality
|
||||
* @return string Complete URL to file.php with thumbnail parameters
|
||||
*/
|
||||
protected static function buildThumbnailUrl(
|
||||
string $rootPath,
|
||||
string $localPath,
|
||||
string $pageId,
|
||||
int $width,
|
||||
int $height,
|
||||
int $quality
|
||||
): string {
|
||||
$params = [
|
||||
'root' => $rootPath,
|
||||
'file' => $localPath,
|
||||
'id' => $pageId,
|
||||
'thumb' => 1,
|
||||
'w' => $width,
|
||||
'h' => $height,
|
||||
'q' => $quality,
|
||||
];
|
||||
return DOKU_BASE . 'lib/plugins/luxtools/file.php?' . http_build_query($params, '', '&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the expected thumbnail cache path.
|
||||
*
|
||||
* Mirrors the hashing scheme in file.php so we can detect whether a thumb
|
||||
* already exists and can be used immediately.
|
||||
*
|
||||
* @param string $path Full filesystem path to the image
|
||||
* @param int $w Width
|
||||
* @param int $h Height
|
||||
* @param int $q Quality (JPEG)
|
||||
* @return string|null Path to cached thumbnail, or null if unavailable
|
||||
*/
|
||||
public static function getCachePath(string $path, int $w, int $h, int $q = 80): ?string
|
||||
{
|
||||
if ($path === '' || !is_file($path)) return null;
|
||||
|
||||
$mtime = @filemtime($path);
|
||||
if ($mtime === false) return null;
|
||||
|
||||
// Decide output format the same way file.php does
|
||||
try {
|
||||
[, $mime,] = mimetype($path, false);
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
if (!is_string($mime) || !str_starts_with($mime, 'image/')) return null;
|
||||
$dstFormat = ($mime === 'image/png' || $mime === 'image/gif') ? 'png' : 'jpg';
|
||||
|
||||
global $conf;
|
||||
if (!isset($conf['cachedir']) || !is_string($conf['cachedir']) || trim($conf['cachedir']) === '') return null;
|
||||
|
||||
$hash = sha1($path . '|' . $mtime . '|w=' . $w . '|h=' . $h . '|q=' . $q . '|f=' . $dstFormat);
|
||||
$sub = substr($hash, 0, 2);
|
||||
$cacheDir = rtrim($conf['cachedir'], '/');
|
||||
return $cacheDir . '/luxtools/thumbs/' . $sub . '/' . $hash . '.' . $dstFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get placeholder image URL.
|
||||
*
|
||||
* @param int $width Desired width
|
||||
* @param int $height Desired height
|
||||
* @param string|null $placeholderId Optional MediaManager ID for custom placeholder
|
||||
* @return string Placeholder URL
|
||||
*/
|
||||
public static function getPlaceholderUrl(int $width, int $height, ?string $placeholderId = null): string
|
||||
{
|
||||
$placeholderUrl = DOKU_BASE . 'lib/images/blank.gif';
|
||||
|
||||
if ($placeholderId !== null && $placeholderId !== '' && function_exists('ml')) {
|
||||
$placeholderUrl = ml($placeholderId, ['w' => $width, 'h' => $height], true, '&');
|
||||
}
|
||||
|
||||
return $placeholderUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the initial image source and data attributes for lazy thumbnail loading.
|
||||
*
|
||||
* Returns placeholder info if thumbnail needs to be loaded, or the actual
|
||||
* thumbnail URL if it's already cached.
|
||||
*
|
||||
* @param string $imagePath Full filesystem path to the image
|
||||
* @param string $thumbUrl URL to the thumbnail
|
||||
* @param int $width Width
|
||||
* @param int $height Height
|
||||
* @param int $quality Quality (JPEG)
|
||||
* @param string|null $placeholderId Optional MediaManager ID for custom placeholder
|
||||
* @return array ['src' => string, 'dataThumbAttr' => string]
|
||||
*/
|
||||
public static function getDisplayInfo(
|
||||
string $imagePath,
|
||||
string $thumbUrl,
|
||||
int $width,
|
||||
int $height,
|
||||
int $quality = 80,
|
||||
?string $placeholderId = null
|
||||
): array {
|
||||
$thumbCachePath = self::getCachePath($imagePath, $width, $height, $quality);
|
||||
|
||||
if ($thumbCachePath !== null && @is_file($thumbCachePath)) {
|
||||
// Thumbnail exists: display it immediately
|
||||
return [
|
||||
'src' => $thumbUrl,
|
||||
'dataThumbAttr' => '',
|
||||
];
|
||||
}
|
||||
|
||||
// Thumbnail doesn't exist: show placeholder and lazy-load
|
||||
$placeholderUrl = self::getPlaceholderUrl($width, $height, $placeholderId);
|
||||
return [
|
||||
'src' => $placeholderUrl,
|
||||
'dataThumbAttr' => ' data-thumb-src="' . hsc($thumbUrl) . '"',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use dokuwiki\Extension\SyntaxPlugin;
|
||||
use dokuwiki\plugin\luxtools\Path;
|
||||
use dokuwiki\plugin\luxtools\ThumbnailHelper;
|
||||
|
||||
require_once(__DIR__ . '/../autoload.php');
|
||||
|
||||
@@ -58,7 +59,7 @@ class syntax_plugin_luxtools_image extends SyntaxPlugin
|
||||
// ?200x150¢er - full options
|
||||
$width = null;
|
||||
$height = null;
|
||||
$align = 'right'; // default alignment
|
||||
$align = null; // Will use default if not specified
|
||||
|
||||
if (strpos($pathPart, '?') !== false) {
|
||||
[$pathPart, $paramStr] = explode('?', $pathPart, 2);
|
||||
@@ -107,6 +108,18 @@ class syntax_plugin_luxtools_image extends SyntaxPlugin
|
||||
return true;
|
||||
}
|
||||
|
||||
// Apply default settings if not explicitly specified
|
||||
if ($data['width'] === null) {
|
||||
$data['width'] = (int)$this->getConf('default_image_width');
|
||||
if ($data['width'] <= 0) $data['width'] = 250;
|
||||
}
|
||||
if ($data['align'] === null) {
|
||||
$data['align'] = (string)$this->getConf('default_image_align');
|
||||
if (!in_array($data['align'], ['left', 'right', 'center'], true)) {
|
||||
$data['align'] = 'right';
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$pathHelper = new Path($this->getConf('paths'));
|
||||
// Use addTrailingSlash=false since this is a file path, not a directory
|
||||
@@ -135,14 +148,23 @@ class syntax_plugin_luxtools_image extends SyntaxPlugin
|
||||
return true;
|
||||
}
|
||||
|
||||
// Build the image URL using the plugin's file.php endpoint
|
||||
// Get thumbnail from helper - it handles everything
|
||||
global $ID;
|
||||
$imageUrl = $this->buildFileUrl($pathInfo, $data['width'], $data['height']);
|
||||
$placeholderId = trim((string)$this->getConf('thumb_placeholder'));
|
||||
$thumb = ThumbnailHelper::getThumbnail(
|
||||
$pathInfo['root'],
|
||||
$pathInfo['local'],
|
||||
$ID,
|
||||
$data['width'] ?? 250,
|
||||
$data['height'] ?? ($data['width'] ?? 250),
|
||||
80,
|
||||
$placeholderId !== '' ? $placeholderId : null
|
||||
);
|
||||
|
||||
// Build full-size URL for linking
|
||||
$fullUrl = $this->buildFileUrl($pathInfo, null, null);
|
||||
$fullUrl = $this->buildFileUrl($pathInfo, null, null, false);
|
||||
|
||||
$this->renderImageBox($renderer, $imageUrl, $fullUrl, $data);
|
||||
$this->renderImageBox($renderer, $thumb, $fullUrl, $data);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -153,9 +175,10 @@ class syntax_plugin_luxtools_image extends SyntaxPlugin
|
||||
* @param array $pathInfo Path info from Path helper
|
||||
* @param int|null $width Optional width
|
||||
* @param int|null $height Optional height
|
||||
* @param bool $thumbnail Whether to generate a thumbnail
|
||||
* @return string
|
||||
*/
|
||||
protected function buildFileUrl(array $pathInfo, ?int $width, ?int $height): string
|
||||
protected function buildFileUrl(array $pathInfo, ?int $width, ?int $height, bool $thumbnail = false): string
|
||||
{
|
||||
global $ID;
|
||||
|
||||
@@ -165,6 +188,12 @@ class syntax_plugin_luxtools_image extends SyntaxPlugin
|
||||
'id' => $ID,
|
||||
];
|
||||
|
||||
if ($thumbnail && ($width !== null || $height !== null)) {
|
||||
// Enable thumbnail mode (same as gallery logic)
|
||||
$params['thumb'] = 1;
|
||||
$params['q'] = 80; // JPEG quality
|
||||
}
|
||||
|
||||
if ($width !== null) {
|
||||
$params['w'] = $width;
|
||||
}
|
||||
@@ -179,11 +208,11 @@ class syntax_plugin_luxtools_image extends SyntaxPlugin
|
||||
* Render the imagebox HTML.
|
||||
*
|
||||
* @param \Doku_Renderer $renderer
|
||||
* @param string $imageUrl URL for the displayed image
|
||||
* @param array $thumb Thumbnail info from ThumbnailHelper::getThumbnail()
|
||||
* @param string $fullUrl URL for the full-size image (on click)
|
||||
* @param array $data Parsed data from handle()
|
||||
*/
|
||||
protected function renderImageBox(\Doku_Renderer $renderer, string $imageUrl, string $fullUrl, array $data): void
|
||||
protected function renderImageBox(\Doku_Renderer $renderer, array $thumb, string $fullUrl, array $data): void
|
||||
{
|
||||
$align = $data['align'] ?? 'right';
|
||||
$caption = $data['caption'] ?? '';
|
||||
@@ -205,8 +234,11 @@ class syntax_plugin_luxtools_image extends SyntaxPlugin
|
||||
$outerStyle = ' style="width: ' . ($width + 10) . 'px;"';
|
||||
}
|
||||
|
||||
// Use thumbnail metadata from helper
|
||||
$dataThumbAttr = $thumb['isFinal'] ? '' : ' data-thumb-src="' . hsc($thumb['thumbUrl']) . '"';
|
||||
|
||||
// Build image attributes
|
||||
$imgAttrs = 'class="media" loading="lazy" decoding="async"';
|
||||
$imgAttrs = 'class="media luxtools-thumb" loading="lazy" decoding="async"';
|
||||
if ($width !== null) {
|
||||
$imgAttrs .= ' width="' . (int)$width . '"';
|
||||
}
|
||||
@@ -221,8 +253,8 @@ class syntax_plugin_luxtools_image extends SyntaxPlugin
|
||||
|
||||
// Image with link to full size
|
||||
$renderer->doc .= '<a href="' . hsc($fullUrl) . '" class="media" target="_blank">';
|
||||
$renderer->doc .= '<img src="' . hsc($imageUrl) . '" ' . $imgAttrs . ' />';
|
||||
$renderer->doc .= '</a>';
|
||||
$renderer->doc .= '<img src="' . hsc($thumb['url']) . '" ' . $imgAttrs . $dataThumbAttr . ' />';
|
||||
$renderer->doc .= '</a>';;
|
||||
|
||||
// Caption
|
||||
if ($caption !== '') {
|
||||
|
||||
Reference in New Issue
Block a user