diff --git a/Crawler.php b/Crawler.php index 9216dac..b0cb486 100644 --- a/Crawler.php +++ b/Crawler.php @@ -13,6 +13,9 @@ class Crawler /** @var bool */ protected $sortreverse = false; + /** @var bool */ + protected $foldersFirst = false; + /** @var string[] patterns to ignore */ protected $ignore = []; @@ -41,6 +44,11 @@ class Crawler $this->sortreverse = $sortreverse; } + public function setFoldersFirst($foldersFirst) + { + $this->foldersFirst = (bool)$foldersFirst; + } + /** * Does a (recursive) crawl for finding files based on a given pattern. * Based on a safe glob reimplementation using fnmatch and opendir. @@ -204,13 +212,41 @@ class Crawler $callback = [$this, 'compare' . ucfirst($this->sortby)]; if (!is_callable($callback)) return $items; - usort($items, $callback); - if ($this->sortreverse) { - $items = array_reverse($items); + // Optional grouping: keep directories before files. + // Implement reverse ordering by inverting comparisons instead of array_reverse(), + // so the directory-first grouping stays intact. + if ($this->foldersFirst) { + usort($items, function ($a, $b) use ($callback) { + $aIsDir = $this->isDirectoryItem($a); + $bIsDir = $this->isDirectoryItem($b); + if ($aIsDir !== $bIsDir) { + return $aIsDir ? -1 : 1; + } + + $cmp = call_user_func($callback, $a, $b); + if ($this->sortreverse) $cmp = -$cmp; + return $cmp; + }); + } else { + usort($items, $callback); + if ($this->sortreverse) { + $items = array_reverse($items); + } } return $items; } + /** + * Detect whether an item represents a directory. + * Supports both crawl() results (children tree) and listDirectory() results (isdir). + */ + protected function isDirectoryItem($item) + { + if (!is_array($item)) return false; + if (!empty($item['isdir'])) return true; + return array_key_exists('children', $item) && $item['children'] !== false; + } + /** * Check if a file is allowed by the configured extensions * diff --git a/Output.php b/Output.php index d84c9fd..d0d4fc8 100644 --- a/Output.php +++ b/Output.php @@ -265,27 +265,47 @@ class Output $renderer->table_open($columns); - if ($params['tableheader']) { + $hasOpenLocation = isset($params['openlocation']) && is_string($params['openlocation']) && trim($params['openlocation']) !== ''; + $hasHeader = !empty($params['tableheader']); + if ($hasOpenLocation || $hasHeader) { $renderer->tablethead_open(); - $renderer->tablerow_open(); - $renderer->tableheader_open(); - $renderer->cdata($this->getLang('filename')); - $renderer->tableheader_close(); + // Small row above the header with an "Open Location" link. + if ($hasOpenLocation && ($renderer instanceof \Doku_Renderer_xhtml)) { + $openItem = [ + 'name' => $this->getLang('openlocation'), + 'path' => $params['openlocation'], + 'isdir' => true, + ]; - if ($params['showsize']) { - $renderer->tableheader_open(); - $renderer->cdata($this->getLang('filesize')); - $renderer->tableheader_close(); + /** @var \Doku_Renderer_xhtml $renderer */ + $renderer->doc .= ''; + $this->renderDirectoryLink($openItem); + $renderer->doc .= ''; } - if ($params['showdate']) { + if ($hasHeader) { + $renderer->tablerow_open(); + $renderer->tableheader_open(); - $renderer->cdata($this->getLang('lastmodified')); + $renderer->cdata($this->getLang('filename')); $renderer->tableheader_close(); + + if ($params['showsize']) { + $renderer->tableheader_open(); + $renderer->cdata($this->getLang('filesize')); + $renderer->tableheader_close(); + } + + if ($params['showdate']) { + $renderer->tableheader_open(); + $renderer->cdata($this->getLang('lastmodified')); + $renderer->tableheader_close(); + } + + $renderer->tablerow_close(); } - $renderer->tablerow_close(); $renderer->tablethead_close(); } diff --git a/lang/de/lang.php b/lang/de/lang.php index 219d5ba..b9d2fd4 100644 --- a/lang/de/lang.php +++ b/lang/de/lang.php @@ -10,5 +10,6 @@ $lang['filename'] = 'Dateiname'; $lang['filesize'] = 'Dateigröße'; $lang['lastmodified'] = 'Letzte Änderung'; +$lang['openlocation'] = 'Ort öffnen'; $lang['error_nomatch'] = 'Keine Treffer'; $lang['error_outsidejail'] = 'Zugriff verweigert'; diff --git a/lang/en/lang.php b/lang/en/lang.php index 982fd2a..918b671 100644 --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -10,5 +10,6 @@ $lang['filename'] = 'Filename'; $lang['filesize'] = 'Filesize'; $lang['lastmodified'] = 'Last modified'; +$lang['openlocation'] = 'Open Location'; $lang['error_nomatch'] = 'No match'; $lang['error_outsidejail'] = 'Access denied'; diff --git a/lang/nl/lang.php b/lang/nl/lang.php index dd4ddaa..6d85d70 100644 --- a/lang/nl/lang.php +++ b/lang/nl/lang.php @@ -9,5 +9,6 @@ $lang['filename'] = 'Bestandsnaam'; $lang['filesize'] = 'Bestandsgrootte'; $lang['lastmodified'] = 'Laatst gewijzigd'; +$lang['openlocation'] = 'Locatie openen'; $lang['error_nomatch'] = 'Niets gevonden'; $lang['error_outsidejail'] = 'Toegang geweigerd'; diff --git a/style.css b/style.css new file mode 100644 index 0000000..65f0148 --- /dev/null +++ b/style.css @@ -0,0 +1,19 @@ +/* LuxTools plugin styles + * Keep this minimal and scoped to the plugin container. + */ + +/* DokuWiki often highlights rows on hover. Avoid highlighting header rows. */ +div.filetools-plugin table thead tr:hover > * { + background-color: @ini_background_alt !important; +} + + +/* "Open Location" row above the header should be visually smaller. */ +div.filetools-plugin table thead tr.luxtools-openlocation-row td { + font-size: 80%; + padding-top: 0.2em; + padding-bottom: 0.2em; +} +div.filetools-plugin table thead tr.luxtools-openlocation-row:hover td { + background-color: @ini_background !important; +} diff --git a/syntax/AbstractSyntax.php b/syntax/AbstractSyntax.php index b8fcfca..2d1e0c6 100644 --- a/syntax/AbstractSyntax.php +++ b/syntax/AbstractSyntax.php @@ -165,6 +165,7 @@ abstract class syntax_plugin_luxtools_abstract extends SyntaxPlugin 'order' => 'asc', 'style' => 'list', 'tableheader' => 0, + 'foldersfirst' => 0, 'recursive' => 0, 'titlefile' => '_title.txt', 'cache' => 0, @@ -222,6 +223,7 @@ abstract class syntax_plugin_luxtools_abstract extends SyntaxPlugin $crawler = new Crawler($this->getConf('extensions')); $crawler->setSortBy($params['sort']); $crawler->setSortReverse($params['order'] === 'desc'); + $crawler->setFoldersFirst(($params['foldersfirst'] ?? 0) != 0); return $crawler; } diff --git a/syntax/directory.php b/syntax/directory.php index 2b8a9ec..a448b7f 100644 --- a/syntax/directory.php +++ b/syntax/directory.php @@ -13,6 +13,15 @@ require_once(__DIR__ . '/AbstractSyntax.php'); */ class syntax_plugin_luxtools_directory extends syntax_plugin_luxtools_abstract { + /** @inheritdoc */ + protected function getDefaultParams(): array + { + return [ + // Directory listings should group folders before files by default. + 'foldersfirst' => 1, + ]; + } + /** @inheritdoc */ protected function getSyntaxKeyword(): string { @@ -35,6 +44,9 @@ class syntax_plugin_luxtools_directory extends syntax_plugin_luxtools_abstract return true; } + // Provide the current directory path so Output can render the "Open Location" link. + $params['openlocation'] = $pathInfo['root'] . $pathInfo['local']; + $crawler = $this->createCrawler($params); $items = $crawler->listDirectory( $pathInfo['root'],