t($actionKey), 'mac' => $mac, 'win' => $win, ]; } return $rows; } private static function matchesHotkeyQueryWith(string $query, array $entries): bool { $query = trim(mb_strtolower($query)); if ($query === '') { return false; } $tokens = preg_split('/[^a-z0-9]+/i', $query); $tokens = is_array($tokens) ? array_values(array_filter($tokens, static fn ($part): bool => $part !== '')) : []; foreach (HotkeyService::searchKeywords() as $keywordRaw) { $keyword = trim(mb_strtolower((string) $keywordRaw)); if ($keyword === '') { continue; } if (in_array($keyword, $tokens, true)) { return true; } foreach ($tokens as $token) { if (strlen($token) >= 3 && str_starts_with($keyword, $token)) { return true; } } if (strlen($query) >= 3 && str_starts_with($keyword, $query)) { return true; } } foreach ($entries as $entry) { $label = mb_strtolower((string) ($entry['label'] ?? '')); $mac = mb_strtolower((string) ($entry['mac'] ?? '')); $win = mb_strtolower((string) ($entry['win'] ?? '')); if (($label !== '' && str_contains($label, $query)) || ($mac !== '' && str_contains($mac, $query)) || ($win !== '' && str_contains($win, $query))) { return true; } } return false; } private static function filterHotkeyEntries(string $query): array { $needle = trim(mb_strtolower($query)); $entries = self::hotkeyEntries(); if (!self::matchesHotkeyQueryWith($query, $entries)) { return []; } if ($needle === '') { return $entries; } $filtered = []; foreach ($entries as $entry) { $label = mb_strtolower((string) ($entry['label'] ?? '')); $mac = mb_strtolower((string) ($entry['mac'] ?? '')); $win = mb_strtolower((string) ($entry['win'] ?? '')); if (str_contains($label, $needle) || str_contains($mac, $needle) || str_contains($win, $needle)) { $filtered[] = $entry; } } return $filtered; } public static function hotkeyPreviewResource(string $query): ?array { $entries = self::filterHotkeyEntries($query); if ($entries === []) { return null; } $items = []; foreach ($entries as $entry) { $items[] = [ 'label' => trim((string) ($entry['label'] ?? '') . ' - ' . (string) ($entry['mac'] ?? '') . ' / ' . (string) ($entry['win'] ?? '')), 'url' => lurl('help/hotkeys'), ]; } return [ 'key' => 'hotkeys', 'label' => t('Keyboard shortcuts'), 'count' => count($items), 'url' => lurl('help/hotkeys'), 'items' => array_slice($items, 0, 5), ]; } public static function hotkeyResultItems(string $query): array { $entries = self::filterHotkeyEntries($query); if ($entries === []) { return []; } $result = []; foreach ($entries as $entry) { $result[] = [ 'type' => t('Keyboard shortcuts'), 'title' => (string) ($entry['label'] ?? ''), 'description' => trim((string) ($entry['mac'] ?? '') . ' / ' . (string) ($entry['win'] ?? '')), 'url' => lurl('help/hotkeys'), 'image' => '', 'icon' => 'bi-keyboard', ]; } return $result; } private static function docsResultUrl(string $slug, string $anchor = ''): string { $url = lurl('admin/docs/' . $slug); if ($anchor !== '') { $url .= '#' . $anchor; } return $url; } public static function docsPreviewResource(string $query): ?array { $matches = DocsCatalogService::search($query); if ($matches === []) { return null; } $seenDocs = []; $previewItems = []; foreach ($matches as $match) { $slug = trim((string) $match['slug']); if ($slug === '') { continue; } $seenDocs[$slug] = true; if (isset($previewItems[$slug])) { continue; } $title = trim((string) $match['title']); if ($title === '') { $title = $slug; } $section = trim((string) $match['section']); $label = $title; if ($section !== '' && mb_strtolower($section) !== mb_strtolower($title)) { $label .= ' - ' . $section; } $previewItems[$slug] = [ 'label' => $label, 'url' => self::docsResultUrl($slug, (string) $match['anchor']), ]; } return [ 'key' => 'docs', 'label' => t('Documentation'), 'count' => count($seenDocs), 'url' => lurl('admin/docs/' . DocsCatalogService::defaultSlug()) . '?search=' . urlencode($query), 'items' => array_values(array_slice($previewItems, 0, 5)), ]; } public static function docsResultItems(string $query): array { $matches = DocsCatalogService::search($query); if ($matches === []) { return []; } $items = []; foreach ($matches as $match) { $slug = trim((string) $match['slug']); $title = trim((string) $match['title']); if ($slug === '' || $title === '') { continue; } $section = trim((string) $match['section']); $snippet = trim((string) $match['snippet']); $descriptionParts = []; if ($section !== '' && mb_strtolower($section) !== mb_strtolower($title)) { $descriptionParts[] = $section; } if ($snippet !== '') { $descriptionParts[] = $snippet; } $rawScore = (int) $match['score']; $searchScore = match (true) { $rawScore >= 1000 => 400, $rawScore >= 900 => 360, $rawScore >= 800 => 330, $rawScore >= 700 => 300, $rawScore >= 650 => 260, $rawScore >= 600 => 230, $rawScore >= 500 => 200, default => 150, }; $items[] = [ 'type' => t('Documentation'), 'title' => $title, 'description' => implode(' - ', $descriptionParts), 'url' => self::docsResultUrl($slug, (string) $match['anchor']), 'image' => '', 'icon' => 'bi-journal-text', 'search_score' => $searchScore, ]; } return $items; } }