From 1b6df4a9e4bbb8b2c170ba12e01071dfdcee83c4 Mon Sep 17 00:00:00 2001 From: luxick Date: Mon, 2 Feb 2026 22:36:38 +0100 Subject: [PATCH] Improved Page lik handling --- action.php | 75 ------------------------ js/page-link.js | 39 +++++-------- lang/de/lang.php | 2 +- lang/en/lang.php | 2 +- style.css | 25 ++------ syntax/AbstractSyntax.php | 53 +++++++++++++++++ syntax/image.php | 63 ++++++++++++++++++--- syntax/open.php | 116 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 245 insertions(+), 130 deletions(-) diff --git a/action.php b/action.php index 17b85b5..8e5e7a2 100644 --- a/action.php +++ b/action.php @@ -3,8 +3,6 @@ use dokuwiki\Extension\ActionPlugin; use dokuwiki\Extension\Event; use dokuwiki\Extension\EventHandler; -use dokuwiki\plugin\luxtools\PageLink; - require_once(__DIR__ . '/autoload.php'); /** @@ -21,12 +19,6 @@ class action_plugin_luxtools extends ActionPlugin $this, "addScripts", ); - $controller->register_hook( - "TPL_CONTENT_DISPLAY", - "BEFORE", - $this, - "addPageLinkStatus", - ); $controller->register_hook( "CSS_STYLES_INCLUDED", "BEFORE", @@ -137,71 +129,4 @@ class action_plugin_luxtools extends ActionPlugin "block" => false, ]; } - - /** - * Inject page link status above the page content. - * - * @param Event $event - * @param mixed $param - * @return void - */ - public function addPageLinkStatus(Event $event, $param) - { - global $ACT, $ID; - - if (!is_string($ACT) || $ACT !== 'show') { - return; - } - - if (!is_string($ID) || $ID === '') { - return; - } - - $pageId = $ID; - if (function_exists('cleanID')) { - $pageId = (string)cleanID($pageId); - } - if ($pageId === '') return; - - $pathConfig = (string)$this->getConf('paths'); - $depth = (int)$this->getConf('pagelink_search_depth'); - if ($depth < 0) $depth = 0; - - $pageLink = new PageLink($pathConfig, $depth); - $uuid = $pageLink->getPageUuid($pageId); - if ($uuid === null) return; - - $linkInfo = $pageLink->resolveUuid($uuid); - $folder = $linkInfo['folder'] ?? null; - $multiple = !empty($linkInfo['multiple']); - - $statusText = ''; - $copyable = false; - $title = ''; - if (is_string($folder) && $folder !== '') { - $trimmed = rtrim($folder, '/\\'); - $statusText = basename($trimmed); - $title = $trimmed; - } else { - $statusText = (string)$this->getLang('pagelink_unlinked'); - $copyable = true; - } - - $warning = ''; - if ($multiple) { - $warning = ''; - } - - $html = '' - . hsc($statusText) - . $warning - . ''; - - $event->data = $html . $event->data; - } } diff --git a/js/page-link.js b/js/page-link.js index c3b72f1..009b4f6 100644 --- a/js/page-link.js +++ b/js/page-link.js @@ -77,10 +77,6 @@ return false; } - function fetchInfo() { - return requestPageLink('info', {}); - } - function ensurePageLink() { return requestPageLink('ensure', { sectok: getSectok() }); } @@ -111,33 +107,24 @@ } catch (e2) {} } - function attachStatus() { - var status = document.querySelector('.luxtools-pagelink-status[data-luxtools-pagelink="1"]'); - if (!status) return; + function attachCopyTargets() { + var targets = document.querySelectorAll('[data-luxtools-pagelink-copy="1"]'); + if (!targets || !targets.length) return; - var pageTitle = document.querySelector('#dokuwiki__content h1') - || document.querySelector('.pageId') - || document.querySelector('h1'); - - if (pageTitle && pageTitle.appendChild) { - status.classList.add('is-inline'); - pageTitle.appendChild(status); - } - - var copy = String(status.getAttribute('data-copy') || '') === '1'; - if (copy) { - status.setAttribute('role', 'button'); - status.setAttribute('tabindex', '0'); - status.addEventListener('click', function (e) { + targets.forEach(function (el) { + if (!el || !el.getAttribute) return; + el.setAttribute('role', 'button'); + el.setAttribute('tabindex', '0'); + el.addEventListener('click', function (e) { e.preventDefault(); - copyToClipboard(String(status.getAttribute('data-uuid') || '').trim()); + copyToClipboard(String(el.getAttribute('data-uuid') || '').trim()); }); - status.addEventListener('keydown', function (e) { + el.addEventListener('keydown', function (e) { if (!e || (e.key !== 'Enter' && e.key !== ' ')) return; e.preventDefault(); - copyToClipboard(String(status.getAttribute('data-uuid') || '').trim()); + copyToClipboard(String(el.getAttribute('data-uuid') || '').trim()); }); - } + }); } window.addBtnActionLuxtoolsPageLink = function ($btn, props, edid) { @@ -178,5 +165,5 @@ return 'luxtools-pagelink'; }; - document.addEventListener('DOMContentLoaded', attachStatus, false); + document.addEventListener('DOMContentLoaded', attachCopyTargets, false); })(); diff --git a/lang/de/lang.php b/lang/de/lang.php index f63fe0f..5e4f046 100644 --- a/lang/de/lang.php +++ b/lang/de/lang.php @@ -78,5 +78,5 @@ $lang["toolbar_code_sample"] = "Ihr Code hier"; $lang["toolbar_datefix_title"] = "Datums-Fix"; $lang["toolbar_datefix_all_title"] = "Datums-Fix (Alle)"; $lang["toolbar_pagelink_title"] = "Seiten-Link"; -$lang["pagelink_unlinked"] = "Nicht verknüpft: ID kopieren"; +$lang["pagelink_unlinked"] = "Seite nicht verknüpft (ID kopieren)"; $lang["pagelink_multi_warning"] = "Mehrere Ordner verknüpft"; diff --git a/lang/en/lang.php b/lang/en/lang.php index d8f1c3c..bd4f74d 100644 --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -78,5 +78,5 @@ $lang["toolbar_code_sample"] = "your code here"; $lang["toolbar_datefix_title"] = "Date Fix"; $lang["toolbar_datefix_all_title"] = "Date Fix (All)"; $lang["toolbar_pagelink_title"] = "Page Link"; -$lang["pagelink_unlinked"] = "Not linked: Copy ID"; +$lang["pagelink_unlinked"] = "Page not linked (Copy ID)"; $lang["pagelink_multi_warning"] = "Multiple folders linked"; diff --git a/style.css b/style.css index d8c09d7..1ff1a21 100644 --- a/style.css +++ b/style.css @@ -49,36 +49,23 @@ div.luxtools-plugin .luxtools-empty { padding: 0.25em 0; } -/* Page link status (next to page title) */ -span.luxtools-pagelink-status { +/* Page link copy message (unlinked blobs alias) */ +a.luxtools-pagelink-copy, +a.luxtools-pagelink-copy:visited { display: inline-flex; align-items: center; - gap: 0.35em; - font-size: 0.75em; - line-height: 1.2; + font-size: 0.85em; + line-height: 1.3; margin: 0.25em 0; padding: 0.15em 0.4em; border: 1px solid @ini_border; border-radius: 0.2em; background-color: @ini_background_alt; color: inherit; -} - -span.luxtools-pagelink-status.is-inline { - margin-left: 0.5em; - margin-top: 0; - margin-bottom: 0; -} - -span.luxtools-pagelink-status[data-copy="1"] { + text-decoration: none; cursor: pointer; } -span.luxtools-pagelink-warning { - font-size: 0.95em; - opacity: 0.8; -} - /* Image gallery spacing. */ div.luxtools-gallery { padding-bottom: 0.5em; diff --git a/syntax/AbstractSyntax.php b/syntax/AbstractSyntax.php index 66fe8ee..0c9e901 100644 --- a/syntax/AbstractSyntax.php +++ b/syntax/AbstractSyntax.php @@ -211,6 +211,10 @@ abstract class syntax_plugin_luxtools_abstract extends SyntaxPlugin try { $pathConfig = (string)$this->getConf('paths'); $blobsRoot = $this->resolveBlobsRoot(); + if ($blobsRoot === '' && $this->isBlobsPath($basePath)) { + $this->renderPageNotLinked($renderer); + return false; + } if ($blobsRoot !== '') { $pathConfig = rtrim($pathConfig) . "\n" . $blobsRoot . "\nA> blobs"; } @@ -222,6 +226,55 @@ abstract class syntax_plugin_luxtools_abstract extends SyntaxPlugin } } + /** + * Check if the given path uses the blobs alias. + */ + protected function isBlobsPath(string $path): bool + { + $trimmed = ltrim($path, '/'); + return preg_match('/^blobs(\/|$)/', $trimmed) === 1; + } + + /** + * Render the "Page not linked" message with copy ID affordance. + */ + protected function renderPageNotLinked(\Doku_Renderer $renderer): void + { + $uuid = $this->getPageUuidSafe(); + $text = (string)$this->getLang('pagelink_unlinked'); + + if ($renderer instanceof \Doku_Renderer_xhtml) { + $renderer->doc .= '' . hsc($text) . ''; + return; + } + + $renderer->cdata('[n/a: ' . $text . ']'); + } + + /** + * Read the current page UUID (if any). + */ + protected function getPageUuidSafe(): string + { + global $ID; + $pageId = is_string($ID) ? $ID : ''; + if ($pageId === '') return ''; + + if (function_exists('cleanID')) { + $pageId = (string)cleanID($pageId); + } + if ($pageId === '') return ''; + + $depth = (int)$this->getConf('pagelink_search_depth'); + if ($depth < 0) $depth = 0; + + $pageLink = new PageLink((string)$this->getConf('paths'), $depth); + $uuid = $pageLink->getPageUuid($pageId); + return $uuid ?? ''; + } + /** * Resolve the current page's pagelink folder for the blobs alias. * diff --git a/syntax/image.php b/syntax/image.php index 1992c82..2a960ef 100644 --- a/syntax/image.php +++ b/syntax/image.php @@ -133,7 +133,18 @@ class syntax_plugin_luxtools_image extends SyntaxPlugin } try { - $pathHelper = new Path($this->buildPathConfigWithBlobs()); + $blobsRoot = $this->resolveBlobsRoot(); + if ($blobsRoot === '' && $this->isBlobsPath($data['path'] ?? '')) { + $this->renderPageNotLinked($renderer); + return true; + } + + $pathConfig = (string)$this->getConf('paths'); + if ($blobsRoot !== '') { + $pathConfig = rtrim($pathConfig) . "\n" . $blobsRoot . "\nA> blobs"; + } + + $pathHelper = new Path($pathConfig); // Use addTrailingSlash=false since this is a file path, not a directory $pathInfo = $pathHelper->getPathInfo($data['path'], false); } catch (\Exception $e) { @@ -216,16 +227,52 @@ class syntax_plugin_luxtools_image extends SyntaxPlugin } /** - * Build a path configuration string, adding the blobs alias if available. + * Check if the given path uses the blobs alias. */ - protected function buildPathConfigWithBlobs(): string + protected function isBlobsPath(string $path): bool { - $pathConfig = (string)$this->getConf('paths'); - $blobsRoot = $this->resolveBlobsRoot(); - if ($blobsRoot !== '') { - $pathConfig = rtrim($pathConfig) . "\n" . $blobsRoot . "\nA> blobs"; + $trimmed = ltrim($path, '/'); + return preg_match('/^blobs(\/|$)/', $trimmed) === 1; + } + + /** + * Render the "Page not linked" message with copy ID affordance. + */ + protected function renderPageNotLinked(\Doku_Renderer $renderer): void + { + $uuid = $this->getPageUuidSafe(); + $text = (string)$this->getLang('pagelink_unlinked'); + + if ($renderer instanceof \Doku_Renderer_xhtml) { + $renderer->doc .= '' . hsc($text) . ''; + return; } - return $pathConfig; + + $renderer->cdata('[n/a: ' . $text . ']'); + } + + /** + * Read the current page UUID (if any). + */ + protected function getPageUuidSafe(): string + { + global $ID; + $pageId = is_string($ID) ? $ID : ''; + if ($pageId === '') return ''; + + if (function_exists('cleanID')) { + $pageId = (string)cleanID($pageId); + } + if ($pageId === '') return ''; + + $depth = (int)$this->getConf('pagelink_search_depth'); + if ($depth < 0) $depth = 0; + + $pageLink = new PageLink((string)$this->getConf('paths'), $depth); + $uuid = $pageLink->getPageUuid($pageId); + return $uuid ?? ''; } /** diff --git a/syntax/open.php b/syntax/open.php index 5d82051..9212c87 100644 --- a/syntax/open.php +++ b/syntax/open.php @@ -1,6 +1,10 @@ isBlobsPath($path)) { + $blobsRoot = $this->resolveBlobsRoot(); + if ($blobsRoot === '') { + $this->renderPageNotLinked($renderer); + return true; + } + + try { + $pathConfig = (string)$this->getConf('paths'); + $pathConfig = rtrim($pathConfig) . "\n" . $blobsRoot . "\nA> blobs"; + $pathHelper = new Path($pathConfig); + $resolvedPath = $path; + $isBlobsRoot = (rtrim($resolvedPath, '/') === 'blobs'); + if ($isBlobsRoot) { + $resolvedPath = rtrim($resolvedPath, '/') . '/'; + } + $pathInfo = $pathHelper->getPathInfo($resolvedPath, $isBlobsRoot); + $path = $pathInfo['path']; + } catch (\Exception $e) { + $renderer->cdata('[n/a: ' . $this->getLang('error_outsidejail') . ']'); + return true; + } + } + + // Map local paths back to their configured aliases before opening. + if (!preg_match('/^[a-zA-Z][a-zA-Z0-9+.-]*:/', $path)) { + try { + $pathHelper = new Path((string)$this->getConf('paths')); + $path = $pathHelper->mapToAliasPath($path); + } catch (\Exception $e) { + // ignore mapping failures + } + } + $serviceUrl = trim((string)$this->getConf('open_service_url')); $serviceToken = trim((string)$this->getConf('open_service_token')); @@ -106,4 +145,81 @@ class syntax_plugin_luxtools_open extends SyntaxPlugin $renderer->doc .= $renderer->_formatLink($link); return true; } + + /** + * Check if the given path uses the blobs alias. + */ + protected function isBlobsPath(string $path): bool + { + $trimmed = ltrim($path, '/'); + return preg_match('/^blobs(\/|$)/', $trimmed) === 1; + } + + /** + * Resolve the current page's pagelink folder for the blobs alias. + */ + protected function resolveBlobsRoot(): string + { + global $ID; + $pageId = is_string($ID) ? $ID : ''; + if ($pageId === '') return ''; + + if (function_exists('cleanID')) { + $pageId = (string)cleanID($pageId); + } + if ($pageId === '') return ''; + + $depth = (int)$this->getConf('pagelink_search_depth'); + if ($depth < 0) $depth = 0; + + $pageLink = new PageLink((string)$this->getConf('paths'), $depth); + $uuid = $pageLink->getPageUuid($pageId); + if ($uuid === null) return ''; + + $linkInfo = $pageLink->resolveUuid($uuid); + $folder = $linkInfo['folder'] ?? ''; + if (!is_string($folder) || $folder === '') return ''; + + return $folder; + } + + /** + * Render the "Page not linked" message with copy ID affordance. + */ + protected function renderPageNotLinked(\Doku_Renderer $renderer): void + { + $uuid = $this->getPageUuidSafe(); + $text = (string)$this->getLang('pagelink_unlinked'); + + if ($renderer instanceof \Doku_Renderer_xhtml) { + $renderer->doc .= '' . hsc($text) . ''; + return; + } + + $renderer->cdata('[n/a: ' . $text . ']'); + } + + /** + * Read the current page UUID (if any). + */ + protected function getPageUuidSafe(): string + { + global $ID; + $pageId = is_string($ID) ? $ID : ''; + if ($pageId === '') return ''; + + if (function_exists('cleanID')) { + $pageId = (string)cleanID($pageId); + } + if ($pageId === '') return ''; + + $depth = (int)$this->getConf('pagelink_search_depth'); + if ($depth < 0) $depth = 0; + + $pageLink = new PageLink((string)$this->getConf('paths'), $depth); + $uuid = $pageLink->getPageUuid($pageId); + return $uuid ?? ''; + } }