Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/modules/blog/src/AppBlogServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Drupal\app_blog;

use Drupal\app_blog\Controller\BlogList;
use Drupal\app_blog\Llms\ArticleLlmsSubscriber;
use Drupal\app_blog\Llms\BlogListLlmsSubscriber;
use Drupal\app_blog\Controller\RssFeed;
use Drupal\app_blog\Controller\RssFeedRedirect;
use Drupal\app_blog\Controller\RssFeedStylesheet;
Expand Down Expand Up @@ -102,6 +104,10 @@ public function register(ContainerBuilder $container): void {

// HTML processor.
$autowire(HtmlProcessor::class);

// Llms.
$autowire(ArticleLlmsSubscriber::class);
$autowire(BlogListLlmsSubscriber::class);
}

}
12 changes: 6 additions & 6 deletions app/modules/blog/src/Controller/BlogList.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ public function __construct(
protected LanguageManagerInterface $languageManager,
) {}

public function load(): array {
$ids = $this->getEntityIds();

return $this->entityTypeManager->getStorage('node')->loadMultiple($ids);
}

protected function getItems(): array {
$items = [];

Expand All @@ -37,12 +43,6 @@ protected function buildPager(): array {
];
}

protected function load(): array {
$ids = $this->getEntityIds();

return $this->entityTypeManager->getStorage('node')->loadMultiple($ids);
}

protected function getEntityIds(): array {
$query = $this
->entityTypeManager
Expand Down
103 changes: 103 additions & 0 deletions app/modules/blog/src/Llms/ArticleLlmsSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

declare(strict_types=1);

namespace Drupal\app_blog\Llms;

use Drupal\app_blog\Node\ArticleBundle;
use Drupal\app_platform\Llms\HtmlToMarkdownConverter;
use Drupal\app_platform\Llms\LlmsRenderEvent;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final readonly class ArticleLlmsSubscriber implements EventSubscriberInterface {

public function __construct(
private TranslationInterface $translation,
private RendererInterface $renderer,
private HtmlToMarkdownConverter $markdownConverter,
) {}

public function onLlmsRender(LlmsRenderEvent $event): void {
if ($event->routeMatch->getRouteName() !== 'entity.node.canonical') {
return;
}

$node = $event->routeMatch->getParameter('node');

if (!$node instanceof ArticleBundle) {
return;
}

$event->addCacheableDependency($node);

$title = $node->getTitle();
if ($title !== NULL) {
$event->setTitle($title);
}

$parts = [];
$parts[] = $this->buildMeta($node, $event);
$parts[] = $this->buildContent($node);

$event->setMarkdown(\implode("\n\n", \array_filter($parts)));
}

#[\Override]
public static function getSubscribedEvents(): array {
return [LlmsRenderEvent::class => 'onLlmsRender'];
}

private function buildMeta(ArticleBundle $node, LlmsRenderEvent $event): string {
$lines = [];

$date_label = (string) $this->translation->translate('Date');
$created = \date('Y-m-d', (int) $node->getCreatedTime());
$lines[] = \sprintf('- **%s**: %s', $date_label, $created);

$readTime = $node->getEstimatedReadTime();
if ($readTime > 0) {
$read_time_label = (string) $this->translation->translate('Estimated read time');
$min_label = (string) $this->translation->translate('@count min', ['@count' => $readTime]);
$lines[] = \sprintf('- **%s**: %s', $read_time_label, $min_label);
}

$tags = $node->get('field_tags')->referencedEntities();
if ($tags !== []) {
$tags_label = (string) $this->translation->translate('Tags');
$lines[] = \sprintf('- **%s**:', $tags_label);

foreach ($tags as $term) {
$event->addCacheableDependency($term);
$url = $term->toUrl()->toString();
$lines[] = \sprintf(' - [%s](%s)', $term->label(), $url);
}
}

return \implode("\n", $lines);
}

private function buildContent(ArticleBundle $node): ?string {
$content = $node->getContent();

if ($content === NULL) {
return NULL;
}

$build = [
'#type' => 'processed_text',
'#text' => $content,
'#format' => 'blog_article',
];

$html = (string) $this->renderer->renderInIsolation($build);

if ($html === '') {
return NULL;
}

return $this->markdownConverter->convert($html);
}

}
61 changes: 61 additions & 0 deletions app/modules/blog/src/Llms/BlogListLlmsSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Drupal\app_blog\Llms;

use Drupal\app_blog\Controller\BlogList;
use Drupal\app_blog\Node\ArticleBundle;
use Drupal\app_platform\Llms\LlmsRenderEvent;
use Drupal\Core\Cache\CacheableMetadata;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final readonly class BlogListLlmsSubscriber implements EventSubscriberInterface {

public function __construct(
private BlogList $blogList,
) {}

public function onLlmsRender(LlmsRenderEvent $event): void {
if ($event->routeMatch->getRouteName() !== 'app_blog.blog_list') {
return;
}

$listCache = new CacheableMetadata();
$listCache->addCacheTags(['node_list:blog_entry']);
$event->addCacheableDependency($listCache);

$lines = [];

foreach ($this->blogList->load() as $node) {
if (!($node instanceof ArticleBundle)) {
continue;
}

$event->addCacheableDependency($node);
$lines[] = $this->formatArticle($node);
}

$event->setMarkdown(\implode("\n", $lines));
}

#[\Override]
public static function getSubscribedEvents(): array {
return [LlmsRenderEvent::class => 'onLlmsRender'];
}

private function formatArticle(ArticleBundle $node): string {
$url = $node->toUrl()->toString();
$title = $node->getTitle() ?? '';
$date = \date('Y-m-d', (int) $node->getCreatedTime());
$body = $node->get('body')->getString();

$line = \sprintf('- [%s](%s) (%s)', $title, $url, $date);
if ($body !== '') {
$line .= \sprintf(" \\\n %s", $body);
}

return $line;
}

}
2 changes: 1 addition & 1 deletion app/modules/blog/src/Node/ArticleBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function getContent(): ?string {
return NULL;
}

return $this->get('field_content')->getString();
return $this->get('field_content')->first()?->get('value')->getString();
}

public function getSourcePathHash(): ?string {
Expand Down
3 changes: 3 additions & 0 deletions app/modules/main/src/AppMainServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Drupal\app_main;

use Drupal\app_main\Llms\HomeLlmsSubscriber;
use Drupal\app_main\Navigation\SiteMap\MainMenuSiteMap;
use Drupal\app_main\Navigation\Toolbar\ContentEditingToolbarLinksBuilder;
use Drupal\app_main\StaticPage\About\Repository\AboutSettings;
Expand All @@ -30,6 +31,8 @@ public function register(ContainerBuilder $container): void {
$autowire(ServicesSettings::class);
$autowire(SupportSettings::class);

$autowire(HomeLlmsSubscriber::class);

$container->register(ContentEditingToolbarLinksBuilder::class)
->setPublic(TRUE)
->addArgument(new Reference('plugin.manager.menu.local_task'))
Expand Down
120 changes: 120 additions & 0 deletions app/modules/main/src/Llms/HomeLlmsSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

declare(strict_types=1);

namespace Drupal\app_main\Llms;

use Drupal\app_blog\Node\ArticleBundle;
use Drupal\app_platform\Llms\LlmsRenderEvent;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Url;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final readonly class HomeLlmsSubscriber implements EventSubscriberInterface {

public function __construct(
private EntityTypeManagerInterface $entityTypeManager,
private TranslationInterface $translation,
) {}

public function onLlmsRender(LlmsRenderEvent $event): void {
if ($event->routeMatch->getRouteName() !== 'app_main.home') {
return;
}

$sections = $event->mainContent['#sections'] ?? [];
$parts = [];

if (isset($sections['home_intro'])) {
$parts[] = (string) $sections['home_intro']['#heading'];
}

$parts[] = $this->buildLinks();

foreach (['latest_posts', 'too_big_to_read', 'most_discussed'] as $section_id) {
$section_markdown = $this->buildSection($sections, $section_id);

if ($section_markdown === NULL) {
continue;
}

$parts[] = $section_markdown;
}

$event->setMarkdown(\implode("\n\n", $parts));
}

#[\Override]
public static function getSubscribedEvents(): array {
return [LlmsRenderEvent::class => 'onLlmsRender'];
}

/**
* @param array<string, mixed> $sections
*/
private function buildSection(array $sections, string $section_id): ?string {
if (!isset($sections[$section_id])) {
return NULL;
}

$section = $sections[$section_id];
\assert(\is_array($section));
$items = $section['#items'] ?? [];
\assert(\is_array($items));
$ids = \array_keys($items);

if ($ids === []) {
return NULL;
}

$nodes = $this->entityTypeManager->getStorage('node')->loadMultiple($ids);
$lines = $this->formatArticles($nodes);

if ($lines === []) {
return NULL;
}

$heading = $section['#heading'] ?? '';
\assert(\is_string($heading) || $heading instanceof \Stringable);
$heading = (string) $heading;

return \sprintf("## %s\n\n%s", $heading, \implode("\n", $lines));
}

private function buildLinks(): string {
$about = Url::fromRoute('app_main.about')->toString();
$blog = Url::fromRoute('app_blog.blog_list')->toString();

return \sprintf(
"- [%s](%s)\n- [%s](%s)",
$this->translation->translate('All publications'),
$blog,
$this->translation->translate('About the author'),
$about,
);
}

/**
* @param array<int|string, \Drupal\Core\Entity\EntityInterface> $nodes
*
* @return list<string>
*/
private function formatArticles(array $nodes): array {
$lines = [];

foreach ($nodes as $node) {
if (!$node instanceof ArticleBundle) {
continue;
}

$url = $node->toUrl()->toString();
$title = $node->getTitle() ?? '';
$date = \date('Y-m-d', (int) $node->getCreatedTime());
$lines[] = \sprintf('- [%s](%s) (%s)', $title, $url, $date);
}

return $lines;
}

}
3 changes: 2 additions & 1 deletion app/modules/platform/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
],
"require": {
"app/contract": "^1.0",
"app-asset/photoswipe": "^5.4"
"app-asset/photoswipe": "^5.4",
"league/html-to-markdown": "^5.1"
}
}
Loading