From 8be9442cb79ca699cbbacef1ac1123b8e4b52871 Mon Sep 17 00:00:00 2001 From: Ruben Carlo Benante Date: Mon, 13 Jul 2026 14:38:31 -0300 Subject: [PATCH] title sort via accent-stripped dashed slug (fixes names, keeps numbers) (v1.9.1) --- Helper/OrganonSortHelper.php | 44 ++++++++++++++++++++++++++++++++---- Plugin.php | 2 +- VERSION | 2 +- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/Helper/OrganonSortHelper.php b/Helper/OrganonSortHelper.php index b4d6af9..5cafd35 100644 --- a/Helper/OrganonSortHelper.php +++ b/Helper/OrganonSortHelper.php @@ -112,9 +112,16 @@ class OrganonSortHelper extends Base } /** - * Number-aware title sort (strnatcasecmp, so "Task 2" precedes "Task 10"), then write positions. - * Mirrors the native reorder scope (all tasks in the column+swimlane, no status filter). Core's - * TaskReorderModel::reorderTasks is protected, so the 3-line position write is replicated here. + * Title sort, then write positions. Mirrors the native reorder scope (all tasks in the + * column+swimlane, no status filter). Core's TaskReorderModel::reorderTasks is protected, so the + * 3-line position write is replicated here. + * + * Each title is reduced to a comparison slug -- lowercased, accent-stripped, spaces -> dashes -- + * then compared with strnatcasecmp. The dash is deliberate: strnatcasecmp SKIPS whitespace, so a + * plain space would make "Luiz Eduardo" sort after "Luiza..." (it would compare "LuizEduardo" vs + * "Luiza..."); a dash is not whitespace, so the separator is honoured, while strnatcasecmp still + * groups digit runs ("Task 2" < "Task 10"). The result is case-insensitive, accent-insensitive, + * whitespace-correct and number-aware. */ private function reorderByTitle($project_id, $swimlane_id, $column_id, $direction) { @@ -125,8 +132,13 @@ class OrganonSortHelper extends Base ->eq('column_id', $column_id) ->findAll(); + foreach ($rows as &$row) { + $row['sortkey'] = str_replace(' ', '-', $this->stripAccents(mb_strtolower((string) $row['title'], 'UTF-8'))); + } + unset($row); + usort($rows, function ($a, $b) use ($direction) { - $cmp = strnatcasecmp((string) $a['title'], (string) $b['title']); + $cmp = strnatcasecmp($a['sortkey'], $b['sortkey']); return $direction === 'desc' ? -$cmp : $cmp; }); @@ -137,4 +149,28 @@ class OrganonSortHelper extends Base $position++; } } + + /** + * Flatten a string for sorting: strip accents to their base letter so accented names sort by that + * base (Avila near A, not after Z). Uses ext-intl Normalizer when available (deterministic and + * locale-independent), falling back to iconv //TRANSLIT (locale-dependent), then the raw string + * (still sorts, just accent-naive). Case is already lowered by the caller. + * + * @param string $s + * @return string + */ + private function stripAccents($s) + { + if (class_exists('Normalizer')) { + $decomposed = \Normalizer::normalize($s, \Normalizer::FORM_D); + + if ($decomposed !== false) { + return preg_replace('/\p{Mn}/u', '', $decomposed); + } + } + + $translit = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); + + return $translit !== false ? $translit : $s; + } } diff --git a/Plugin.php b/Plugin.php index ea669b6..896367f 100644 --- a/Plugin.php +++ b/Plugin.php @@ -162,7 +162,7 @@ class Plugin extends Base public function getPluginVersion() { - return '1.9.0'; + return '1.9.1'; } public function getPluginHomepage() diff --git a/VERSION b/VERSION index 888fe7d..6c2b60b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -OrganonTweaks v1.9.0 +OrganonTweaks v1.9.1