1
0
Files
breadcrumb-the-shire/lib/Support/Search/SearchSpecialResourceProvider.php
2026-03-06 00:44:52 +01:00

254 lines
7.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace MintyPHP\Support\Search;
use MintyPHP\Service\Docs\DocsCatalogService;
use MintyPHP\Service\Ui\HotkeyService;
class SearchSpecialResourceProvider
{
private static function hotkeyEntries(): array
{
$rows = [];
foreach (HotkeyService::list() as $row) {
$actionKey = trim($row['action_key']);
$mac = trim($row['mac']);
$win = trim($row['win']);
if ($actionKey === '' || $mac === '' || $win === '') {
continue;
}
$rows[] = [
'label' => t($actionKey),
'mac' => $mac,
'win' => $win,
];
}
return $rows;
}
// Two-tier match: first checks against the hotkey service's keyword list (e.g. "shortcut", "keyboard"),
// then falls back to substring matching against individual entry labels/key combos.
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;
}
// Map raw doc scores (01000+) to coarser search_score buckets for result ranking.
$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;
}
}