From bea0ab9390c823c316cb8084a83a6a83c28388bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Tue, 16 Sep 2025 13:41:23 +0200 Subject: [PATCH] Make search more prominent --- .../search_broadcast_controller.js | 9 ++++++ .../search_visibility_controller.js | 12 +++++++ assets/styles/form.css | 5 +++ assets/styles/utilities.css | 2 ++ config/packages/security.yaml | 2 +- .../NostrEventFromYamlDefinitionCommand.php | 3 +- src/Controller/DefaultController.php | 9 +++++- src/Controller/MagazineWizardController.php | 21 +++++++++++-- src/Twig/Components/SearchComponent.php | 8 +++-- templates/components/Molecules/Card.html.twig | 2 +- .../components/Organisms/ZineList.html.twig | 28 ++++++++++------- .../components/SearchComponent.html.twig | 31 +++++++++++++------ templates/components/UserMenu.html.twig | 5 ++- templates/home.html.twig | 17 ++++++---- templates/pages/magazine.html.twig | 21 +++++++++++++ translations/messages.en.yaml | 1 + 16 files changed, 138 insertions(+), 38 deletions(-) create mode 100644 assets/controllers/search_broadcast_controller.js create mode 100644 assets/controllers/search_visibility_controller.js create mode 100644 templates/pages/magazine.html.twig diff --git a/assets/controllers/search_broadcast_controller.js b/assets/controllers/search_broadcast_controller.js new file mode 100644 index 0000000..fca46da --- /dev/null +++ b/assets/controllers/search_broadcast_controller.js @@ -0,0 +1,9 @@ +import { Controller } from '@hotwired/stimulus'; + +export default class extends Controller { + onInput(e) { + const value = e.target.value || ''; + const active = value.trim().length > 0; + window.dispatchEvent(new CustomEvent('search:changed', { detail: { active } })); + } +} diff --git a/assets/controllers/search_visibility_controller.js b/assets/controllers/search_visibility_controller.js new file mode 100644 index 0000000..cc78fc9 --- /dev/null +++ b/assets/controllers/search_visibility_controller.js @@ -0,0 +1,12 @@ +import { Controller } from '@hotwired/stimulus'; + +export default class extends Controller { + static targets = ['list']; + + toggle(event) { + const active = !!event.detail?.active; + if (this.hasListTarget) { + this.listTarget.hidden = active; + } + } +} diff --git a/assets/styles/form.css b/assets/styles/form.css index 0f38c60..ec29103 100644 --- a/assets/styles/form.css +++ b/assets/styles/form.css @@ -67,3 +67,8 @@ textarea, input { #editor { margin: 0; } + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} diff --git a/assets/styles/utilities.css b/assets/styles/utilities.css index 8bddfb9..59d98ac 100644 --- a/assets/styles/utilities.css +++ b/assets/styles/utilities.css @@ -45,3 +45,5 @@ /* Details/Summary tweaks */ details>summary{cursor:pointer} +/* Text truncation with ellipsis */ +.line-clamp-5 {display: -webkit-box;-webkit-box-orient: vertical;-webkit-line-clamp: 5;overflow: hidden;text-overflow: ellipsis;} diff --git a/config/packages/security.yaml b/config/packages/security.yaml index b32a818..5e91441 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -27,6 +27,6 @@ security: # Note: Only the *first* access control that matches will be used access_control: - { path: ^/admin, roles: ROLE_USER } - - { path: ^/search, roles: ROLE_USER } + # - { path: ^/search, roles: ROLE_USER } # - { path: ^/nzine, roles: ROLE_USER } # - { path: ^/profile, roles: ROLE_USER } diff --git a/src/Command/NostrEventFromYamlDefinitionCommand.php b/src/Command/NostrEventFromYamlDefinitionCommand.php index 9dd51de..b8b341c 100644 --- a/src/Command/NostrEventFromYamlDefinitionCommand.php +++ b/src/Command/NostrEventFromYamlDefinitionCommand.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Command; use App\Enum\IndexStatusEnum; +use App\Enum\KindsEnum; use App\Factory\ArticleFactory; use App\Service\NostrClient; use Doctrine\ORM\EntityManagerInterface; @@ -71,7 +72,7 @@ class NostrEventFromYamlDefinitionCommand extends Command try { // Deserialize YAML content into an Event object $event = new Event(); - $event->setKind(30040); + $event->setKind(KindsEnum::PUBLICATION_INDEX->value); $tags = $yamlContent['tags']; $event->setTags($tags); $items = array_filter($tags, function ($tag) { diff --git a/src/Controller/DefaultController.php b/src/Controller/DefaultController.php index cee4c44..4a6b875 100644 --- a/src/Controller/DefaultController.php +++ b/src/Controller/DefaultController.php @@ -19,6 +19,7 @@ use Psr\Log\LoggerInterface; class DefaultController extends AbstractController { public function __construct( + private readonly FinderInterface $finder, private readonly CacheInterface $redisCache) { } @@ -30,6 +31,11 @@ class DefaultController extends AbstractController #[Route('/', name: 'home')] public function index(): Response { + // get latest articles + $q = new Query(); + $q->setSize(12); + $q->setSort(['createdAt' => ['order' => 'desc']]); + $latest = $this->finder->find($q); // get newsroom index, loop over categories, pick top three from each and display in sections $mag = $this->redisCache->get('magazine-newsroom-magazine-by-newsroom', function (){ return null; @@ -41,7 +47,8 @@ class DefaultController extends AbstractController }); return $this->render('home.html.twig', [ - 'indices' => array_values($cats) + 'indices' => array_values($cats), + 'latest' => $latest ]); } diff --git a/src/Controller/MagazineWizardController.php b/src/Controller/MagazineWizardController.php index 66a91cb..fe0bf24 100644 --- a/src/Controller/MagazineWizardController.php +++ b/src/Controller/MagazineWizardController.php @@ -6,9 +6,11 @@ namespace App\Controller; use App\Dto\CategoryDraft; use App\Dto\MagazineDraft; +use App\Enum\KindsEnum; use App\Form\CategoryArticlesType; use App\Form\MagazineSetupType; use App\Service\RedisCacheService; +use Doctrine\ORM\EntityManagerInterface; use Psr\Cache\CacheItemPoolInterface; use swentel\nostr\Event\Event; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -164,7 +166,8 @@ class MagazineWizardController extends AbstractController Request $request, CacheItemPoolInterface $redisCache, CsrfTokenManagerInterface $csrfTokenManager, - RedisClient $redis + RedisClient $redis, + EntityManagerInterface $entityManager ): JsonResponse { // Verify CSRF token $csrfToken = $request->headers->get('X-CSRF-TOKEN'); @@ -184,7 +187,7 @@ class MagazineWizardController extends AbstractController $eventObj->setId($signedEvent['id'] ?? ''); $eventObj->setPublicKey($signedEvent['pubkey'] ?? ''); $eventObj->setCreatedAt($signedEvent['created_at'] ?? time()); - $eventObj->setKind($signedEvent['kind'] ?? 30040); + $eventObj->setKind($signedEvent['kind'] ?? KindsEnum::PUBLICATION_INDEX->value); $eventObj->setTags($signedEvent['tags'] ?? []); $eventObj->setContent($signedEvent['content'] ?? ''); $eventObj->setSignature($signedEvent['sig'] ?? ''); @@ -230,6 +233,20 @@ class MagazineWizardController extends AbstractController // non-fatal } + // Save to persistence as Event entity + // Map swentel Event to Event entity, it's always a new event + $event = new \App\Entity\Event(); + $event->setId($eventObj->getId()); + $event->setPubkey($eventObj->getPublicKey()); + $event->setCreatedAt($eventObj->getCreatedAt()); + $event->setKind($eventObj->getKind()); + $event->setTags($eventObj->getTags()); + $event->setContent($eventObj->getContent()); + $event->setSig($eventObj->getSignature()); + // Persist + $entityManager->persist($event); + $entityManager->flush(); + return new JsonResponse(['ok' => true]); } diff --git a/src/Twig/Components/SearchComponent.php b/src/Twig/Components/SearchComponent.php index d074515..70bc41b 100644 --- a/src/Twig/Components/SearchComponent.php +++ b/src/Twig/Components/SearchComponent.php @@ -28,6 +28,7 @@ final class SearchComponent public array $results = []; public bool $interactive = true; + public string $currentRoute; public int $credits = 0; public ?string $npub = null; @@ -55,8 +56,9 @@ final class SearchComponent { } - public function mount(): void + public function mount($currentRoute = 'search'): void { + $this->currentRoute = $currentRoute; $token = $this->tokenStorage->getToken(); $this->npub = $token?->getUserIdentifier(); @@ -71,7 +73,7 @@ final class SearchComponent } // Restore search results from session if available and no query provided - if (empty($this->query)) { + if (empty($this->query) && $this->currentRoute == 'search') { $session = $this->requestStack->getSession(); if ($session->has(self::SESSION_QUERY_KEY)) { $this->query = $session->get(self::SESSION_QUERY_KEY); @@ -237,7 +239,7 @@ final class SearchComponent ]); // Increase minimum score to filter out irrelevant results - $mainQuery->setMinScore(0.3); + $mainQuery->setMinScore(0.35); // Sort by score and createdAt $mainQuery->setSort([ diff --git a/templates/components/Molecules/Card.html.twig b/templates/components/Molecules/Card.html.twig index 21f4008..e6904d0 100644 --- a/templates/components/Molecules/Card.html.twig +++ b/templates/components/Molecules/Card.html.twig @@ -20,7 +20,7 @@

{{ article.title }}

{% if article.summary %} -

+

{{ article.summary }}

{% endif %} diff --git a/templates/components/Organisms/ZineList.html.twig b/templates/components/Organisms/ZineList.html.twig index 8c867cd..b8eb23c 100644 --- a/templates/components/Organisms/ZineList.html.twig +++ b/templates/components/Organisms/ZineList.html.twig @@ -1,16 +1,22 @@
{% for item in nzines %} - {% if item.npub in indices|keys %} - {% set idx = indices[item.npub] %} - {% if idx|length > 0 %} - - + +
{% endfor %}
diff --git a/templates/components/SearchComponent.html.twig b/templates/components/SearchComponent.html.twig index 8957f1c..99b7385 100644 --- a/templates/components/SearchComponent.html.twig +++ b/templates/components/SearchComponent.html.twig @@ -7,17 +7,37 @@ placeholder="{{ 'text.search'|trans }}" data-model="norender|query" value="{{ this.query }}" + data-controller="search-broadcast" + data-action="input->search-broadcast#onInput" /> - + + {% if is_granted('IS_AUTHENTICATED_FULLY') %}
{{ 'credit.balance'|trans({'%count%': credits, 'count': credits}) }}
+ {% endif %} - + {% if not is_granted('IS_AUTHENTICATED_FULLY') %} +
+

Log in to search articles.

+ Currently only available on desktop. +
+ {% endif %} + + + {% if is_granted('IS_AUTHENTICATED_FULLY') and credits == 0 %} +
+ +
+ {% endif %} + +
@@ -31,11 +51,4 @@ {% elseif this.query is not empty %}

{{ 'text.noResults'|trans }}

{% endif %} - - {% block aside %} - {% if credits == 0 %} - - {% endif %} - {% endblock %}
- diff --git a/templates/components/UserMenu.html.twig b/templates/components/UserMenu.html.twig index b4ce2a0..a5c0a0c 100644 --- a/templates/components/UserMenu.html.twig +++ b/templates/components/UserMenu.html.twig @@ -25,10 +25,9 @@ {% else %} -
-

Log in to access search.

+
+ {{ 'heading.logIn'|trans }}
- {{ 'heading.logIn'|trans }} {% endif %}
diff --git a/templates/home.html.twig b/templates/home.html.twig index ddfe0b0..4dac114 100644 --- a/templates/home.html.twig +++ b/templates/home.html.twig @@ -4,13 +4,18 @@ {% endblock %} {% block body %} - {# content #} - {% for item in indices %} - - {% endfor %} +
+ +
+ +
+
{% endblock %} {% block aside %} -{#
Magazines
#} -{# #} +
Magazines
+ {% endblock %} diff --git a/templates/pages/magazine.html.twig b/templates/pages/magazine.html.twig new file mode 100644 index 0000000..e1c4dc5 --- /dev/null +++ b/templates/pages/magazine.html.twig @@ -0,0 +1,21 @@ +{% extends 'base.html.twig' %} + +{% block ogtags %} + + + + + +{% endblock %} + +{% block nav %} +{% endblock %} + +{% block body %} + {# hero #} + + {# content #} + {% for item in indices %} + + {% endfor %} +{% endblock %} diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml index 8e3d628..95582fe 100644 --- a/translations/messages.en.yaml +++ b/translations/messages.en.yaml @@ -3,6 +3,7 @@ text: search: 'Search...' searching: 'Searching...' noResults: 'No results.' + searchMobileNotice: 'Search is currently only available on desktop for logged-in users.' heading: roles: 'Roles' logout: 'Log out'