Render open-location syntax as link
Some checks failed
DokuWiki Default Tasks / all (push) Has been cancelled

This commit is contained in:
2026-01-07 11:42:40 +01:00
parent 6a396ce511
commit c5f4bcc1c5
7 changed files with 156 additions and 16 deletions

View File

@@ -245,9 +245,9 @@ class plugin_luxtools_test extends DokuWikiTest
} }
/** /**
* This function checks that the open syntax renders an inline button. * This function checks that the open syntax renders an inline link.
*/ */
public function test_open_button() public function test_open_link()
{ {
$instructions = p_get_instructions('{{open>/tmp/somewhere|Open here}}'); $instructions = p_get_instructions('{{open>/tmp/somewhere|Open here}}');
$xhtml = p_render('xhtml', $instructions, $info); $xhtml = p_render('xhtml', $instructions, $info);
@@ -256,7 +256,7 @@ class plugin_luxtools_test extends DokuWikiTest
$doc->html($xhtml); $doc->html($xhtml);
$structure = [ $structure = [
'button.luxtools-open' => 1, 'a.luxtools-open' => 1,
]; ];
$this->structureCheck($doc, $structure); $this->structureCheck($doc, $structure);

49
images/folder.svg Normal file
View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 100 100"
version="1.1"
id="svg3"
sodipodi:docname="folder.svg"
xml:space="preserve"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs3" /><sodipodi:namedview
id="namedview3"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="5.7134228"
inkscape:cx="66.860097"
inkscape:cy="56.008458"
inkscape:window-width="1633"
inkscape:window-height="1059"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg3" /><style
id="style1">.st1{fill:#a87d45}</style><path
fill="#fff"
d="M0 0h100v100H0z"
id="path1" /><path
class="st1"
d="M100 100H0V0h100v100zM9.7 90h80.7V10H9.7"
id="path2" /><path
d="m 75.489689,40.94749 v -5.60846 c 0,-1.548724 -1.255453,-2.804239 -2.80423,-2.804239 H 44.643148 L 41.838918,26.92633 H 27.817766 l -2.212116,4.424267 c -0.389393,0.77876 -0.592114,1.637504 -0.592114,2.508186 v 7.088707"
stroke="#000000"
stroke-linecap="round"
stroke-linejoin="round"
id="path1-3"
style="fill:#a87d45;stroke-width:2.80423;fill-opacity:1;stroke:#ffffff;stroke-opacity:1" /><path
d="m 27.574388,71.794032 h 45.354476 c 1.450348,0 2.661499,-1.105996 2.792732,-2.550454 L 78.016021,44.005505 C 78.165205,42.363397 76.872176,40.94749 75.223289,40.94749 H 25.280016 c -1.648967,0 -2.941996,1.415907 -2.792706,3.058015 l 2.294372,25.238073 c 0.131321,1.444458 1.342358,2.550454 2.792706,2.550454 z"
stroke="#000000"
stroke-linecap="round"
stroke-linejoin="round"
id="path2-6"
style="fill:#a87d45;stroke-width:2.804;fill-opacity:1;stroke-dasharray:none;stroke:#ffffff;stroke-opacity:1" /></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

43
images/open-folder.svg Normal file
View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 100 100"
version="1.1"
id="svg3"
sodipodi:docname="open-folder.svg"
xml:space="preserve"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs3" /><sodipodi:namedview
id="namedview3"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="5.7134228"
inkscape:cx="67.210149"
inkscape:cy="56.183484"
inkscape:window-width="1633"
inkscape:window-height="1059"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg3" /><style
id="style1">.st1{fill:#a87d45}</style><path
d="m 85.019893,39.020089 v -7.70915 c 0,-2.12881 -1.725693,-3.854588 -3.854577,-3.854588 H 42.619548 l -3.854576,-7.70915 H 19.492093 l -3.04068,6.081408 c -0.535243,1.07045 -0.813896,2.250843 -0.813896,3.447646 v 9.743834"
stroke="#000000"
stroke-linecap="round"
stroke-linejoin="round"
id="path1-3"
style="fill:#ebc8ab;fill-opacity:0;stroke:#888888;stroke-width:8;stroke-opacity:1;stroke-dasharray:none" /><path
d="m 19.157556,81.420435 h 62.342335 c 1.993587,0 3.658383,-1.520255 3.838771,-3.505746 L 88.492481,43.223508 C 88.697542,40.966336 86.9202,39.020089 84.65371,39.020089 h -68.6499 c -2.266601,0 -4.043943,1.946247 -3.838736,4.203419 l 3.153747,34.691181 c 0.180508,1.985491 1.845149,3.505746 3.838735,3.505746 z"
stroke="#000000"
stroke-linecap="round"
stroke-linejoin="round"
id="path2-6"
style="fill:#ebc8ab;fill-opacity:0;stroke:#888888;stroke-width:8;stroke-dasharray:none;stroke-opacity:1" /></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -9,4 +9,4 @@ $lang['thumb_placeholder'] = 'MediaManager ID for the gallery thumbnail placehol
$lang['gallery_thumb_scale'] = 'Gallery thumbnail scale factor. Use 2 for sharper thumbnails on HiDPI screens (still displayed as 150×150).'; $lang['gallery_thumb_scale'] = 'Gallery thumbnail scale factor. Use 2 for sharper thumbnails on HiDPI screens (still displayed as 150×150).';
$lang['open_service_url'] = 'Local client service URL for the {{open>...}} button (e.g. http://127.0.0.1:8765).'; $lang['open_service_url'] = 'Local client service URL for the {{open>...}} link (e.g. http://127.0.0.1:8765).';

View File

@@ -119,9 +119,23 @@
return path; return path;
} }
function findOpenElement(target) {
var el = target;
while (el && el !== document) {
if (el.classList && el.classList.contains('luxtools-open')) return el;
el = el.parentNode;
}
return null;
}
function onClick(event) { function onClick(event) {
var el = event.target; var el = findOpenElement(event.target);
if (!el || !el.classList || !el.classList.contains('luxtools-open')) return; if (!el) return;
// {{open>...}} renders as a link; avoid jumping to '#'.
if (el.tagName && el.tagName.toLowerCase() === 'a') {
event.preventDefault();
}
var raw = el.getAttribute('data-path') || ''; var raw = el.getAttribute('data-path') || '';
if (!raw) return; if (!raw) return;

View File

@@ -21,13 +21,25 @@ div.luxtools-plugin table thead tr.luxtools-openlocation-row:hover td {
/* Ensure directories use a dedicated folder icon. /* Ensure directories use a dedicated folder icon.
* DokuWiki's icon CSS is generated primarily for file extensions; a custom * DokuWiki's icon CSS is generated primarily for file extensions; a custom
* mf_folder class may otherwise fall back to the generic file icon. * mf_folder class may otherwise fall back to the generic file icon.
*
* The relative URL is resolved against the plugin directory by DokuWiki's CSS
* aggregator, resulting in lib/images/fileicons/svg/folder.svg.
*/ */
div.luxtools-plugin a.media.mediafile.mf_folder, div.luxtools-plugin a.media.mediafile.mf_folder,
div.luxtools-plugin a.mediafile.mf_folder { div.luxtools-plugin a.mediafile.mf_folder {
background-image: url(../../images/fileicons/svg/folder.svg) !important; background-image: url(images/folder.svg) !important;
}
/* DokuWiki templates often style .media links with higher specificity.
* Ensure our custom color always wins.
*/
div.luxtools-plugin a.luxtools-open,
div.luxtools-plugin a.luxtools-open:visited,
a.luxtools-open,
a.luxtools-open:visited {
color: #b57d35 !important;
}
/* Standalone {{open>...}} links are not wrapped in div.luxtools-plugin. */
a.luxtools-open.media.mediafile.mf_folder {
background-image: url(images/open-folder.svg) !important;
} }
/* Muted empty-state message when a listing has no results. */ /* Muted empty-state message when a listing has no results. */

View File

@@ -5,7 +5,7 @@ use dokuwiki\Extension\SyntaxPlugin;
/** /**
* luxtools Plugin: Open local path syntax. * luxtools Plugin: Open local path syntax.
* *
* Renders an inline button. Clicking it triggers client-side JS that attempts * Renders an inline link. Clicking it triggers client-side JS that attempts
* to open the configured path in the default file manager (best-effort). * to open the configured path in the default file manager (best-effort).
*/ */
class syntax_plugin_luxtools_open extends SyntaxPlugin class syntax_plugin_luxtools_open extends SyntaxPlugin
@@ -74,14 +74,36 @@ class syntax_plugin_luxtools_open extends SyntaxPlugin
} }
$serviceUrl = trim((string)$this->getConf('open_service_url')); $serviceUrl = trim((string)$this->getConf('open_service_url'));
$serviceToken = trim((string)$this->getConf('open_service_token'));
$attrs = ' type="button" class="luxtools-open"' if (!($renderer instanceof \Doku_Renderer_xhtml)) {
. ' data-path="' . hsc($path) . '"'; $renderer->cdata($caption);
if ($serviceUrl !== '') { return true;
$attrs .= ' data-service-url="' . hsc($serviceUrl) . '"';
} }
$renderer->doc .= '<button' . $attrs . '>' . hsc($caption) . '</button>'; global $conf;
/** @var \Doku_Renderer_xhtml $renderer */
// Render like a normal DokuWiki link with an icon in front.
// Use the same folder icon class as directory listings.
$link = [
'target' => $conf['target']['extern'],
'style' => '',
'pre' => '',
'suf' => '',
'name' => $caption,
'url' => '#',
'title' => $renderer->_xmlEntities($path),
'more' => '',
'class' => 'luxtools-open media mediafile mf_folder',
];
$link['more'] .= ' data-path="' . hsc($path) . '"';
if (!empty($conf['relnofollow'])) $link['more'] .= ' rel="nofollow"';
if ($serviceUrl !== '') $link['more'] .= ' data-service-url="' . hsc($serviceUrl) . '"';
if ($serviceToken !== '') $link['more'] .= ' data-service-token="' . hsc($serviceToken) . '"';
$renderer->doc .= $renderer->_formatLink($link);
return true; return true;
} }
} }