- AdditionnalData for buttons
- refresh manga metadata and chapters
This commit is contained in:
Jérémy Guillot
2024-07-05 19:03:16 +02:00
parent 3012adfee7
commit 586ebdb126
14 changed files with 283 additions and 107 deletions

View File

@@ -9,8 +9,32 @@ export default class extends Controller {
currentStatus: String currentStatus: String
} }
refreshMetadata() { refreshMetadata(event) {
console.log("Refreshing..."); const mangaId = event.currentTarget.dataset.mangaid;
const url = `/refresh_metadata`;
fetch(url, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json',
},
body: JSON.stringify({ mangaId: mangaId })
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log('Metadata refreshed:', data);
// Traitez la réponse ici, par exemple en mettant à jour l'interface utilisateur
})
.catch(error => {
console.error('Error:', error);
// Gérez l'erreur ici, par exemple en affichant un message à l'utilisateur
});
} }
searchLastChapter() { searchLastChapter() {

View File

@@ -15,6 +15,7 @@ framework:
routing: routing:
# Route your messages to the transports # Route your messages to the transports
'App\Message\DownloadChapter': async 'App\Message\DownloadChapter': async
'App\Message\RefreshMetadata': async
# when@test: # when@test:
# framework: # framework:

View File

@@ -6,6 +6,7 @@ use App\Entity\Chapter;
use App\Entity\Manga; use App\Entity\Manga;
use App\Manager\Toolbar\Factory\ToolbarFactory; use App\Manager\Toolbar\Factory\ToolbarFactory;
use App\Message\DownloadChapter; use App\Message\DownloadChapter;
use App\Message\RefreshMetadata;
use App\Repository\ChapterRepository; use App\Repository\ChapterRepository;
use App\Repository\MangaRepository; use App\Repository\MangaRepository;
use App\Service\CbzService; use App\Service\CbzService;
@@ -33,8 +34,7 @@ class MangaController extends AbstractController
private readonly CbzService $cbzService, private readonly CbzService $cbzService,
private readonly ToolbarFactory $toolbarFactory, private readonly ToolbarFactory $toolbarFactory,
private readonly MangadexProvider $mangadexProvider, private readonly MangadexProvider $mangadexProvider,
private readonly EntityManagerInterface $entityManager, private readonly EntityManagerInterface $entityManager
private readonly NotificationService $notificationService
) )
{ {
} }
@@ -92,11 +92,10 @@ class MangaController extends AbstractController
} }
return $b <=> $a; return $b <=> $a;
}); });
return $this->render('manga/show_chapters.html.twig', [ return $this->render('manga/show_chapters.html.twig', [
'chapters_by_volume' => $chaptersByVolume, 'chapters_by_volume' => $chaptersByVolume,
'manga' => $manga, 'manga' => $manga,
'toolbar' => $this->toolbarFactory->createToolbar('chapter_list')->getGroups(), 'toolbar' => $this->toolbarFactory->createToolbar('chapter_list', ['mangaId' => $manga->getId()])->getGroups(),
]); ]);
} }
@@ -167,39 +166,12 @@ class MangaController extends AbstractController
->setRating($request->request->get('rating')) ->setRating($request->request->get('rating'))
->setExternalId($request->request->get('externalId')); ->setExternalId($request->request->get('externalId'));
$mangaFeed = $this->mangadexProvider->getFeed($manga); $mergedChapters = $this->mangadexProvider->addAllChaptersToManga($manga);
$mangaAggregate = $this->mangadexProvider->getMangaAggregate($manga);
$allChapters = array_merge($mangaFeed, $mangaAggregate); if (empty($mergedChapters)) {
if (empty($allChapters)) {
$this->notificationService->sendUpdate([
'status' => 'error',
'message' => 'No chapters found for this manga.'
]);
return $this->redirectToRoute('app_manga_search', ['query' => $manga->getTitle()]); return $this->redirectToRoute('app_manga_search', ['query' => $manga->getTitle()]);
} }
$mergedChapters = [];
foreach ($allChapters as $chapter) {
$number = $chapter->getNumber();
if (isset($mergedChapters[$number])) {
$existingChapter = $mergedChapters[$number];
if (!empty($chapter->getExternalId()) ||
(empty($existingChapter->getExternalId()) && !strpos($chapter->getTitle(), 'Chapter ') == 0)) {
$mergedChapters[$number] = $chapter;
}
} else {
$mergedChapters[$number] = $chapter;
}
}
foreach ($mergedChapters as $chapter) {
$manga->addChapter($chapter);
}
try { try {
foreach ($manga->getChapters() as $chapter) { foreach ($manga->getChapters() as $chapter) {
$this->entityManager->persist($chapter); $this->entityManager->persist($chapter);
@@ -264,6 +236,19 @@ class MangaController extends AbstractController
return $response; return $response;
} }
#[Route('/refresh_metadata', name: 'refresh_metadata')]
public function refreshMetadata(Request $request): JsonResponse
{
$mangaId = json_decode($request->getContent(), true)['mangaId'];
$manga = $this->mangaRepository->find($mangaId);
if (!$manga) {
return new JsonResponse(['error' => 'Manga Not Found.'], 400);
}
$this->bus->dispatch(new RefreshMetadata($mangaId));
return new JsonResponse(['success' => 'Metadata refresh started...'], 200);
}
private function isFullVolume(Chapter $chapter): bool private function isFullVolume(Chapter $chapter): bool
{ {
$volumeChapters = $this->chapterRepository->findBy([ $volumeChapters = $this->chapterRepository->findBy([

View File

@@ -7,19 +7,19 @@ use App\Manager\Toolbar\Element\ToolbarDivider;
class ChapterListToolbar extends Toolbar class ChapterListToolbar extends Toolbar
{ {
public function __construct() public function __construct(array $contextData = [])
{ {
$this $this
->addToLeftGroup(new ToolbarButton('arrows-rotate', 'Refresh metadata', 'refreshMetadata')) ->addToLeftGroup(new ToolbarButton('arrows-rotate', 'Refresh metadata', 'refreshMetadata', $contextData))
->addToLeftGroup(new ToolbarDivider()) ->addToLeftGroup(new ToolbarDivider())
->addToLeftGroup(new ToolbarButton('keyboard', 'Rename chapters', 'renameChapters')) ->addToLeftGroup(new ToolbarButton('keyboard', 'Rename chapters', 'renameChapters'))
->addToLeftGroup(new ToolbarButton('file-zipper', 'Manage cbz', 'manageCbz')) ->addToLeftGroup(new ToolbarButton('file-zipper', 'Manage cbz', 'manageCbz', $contextData))
->addToLeftGroup(new ToolbarButton('history', 'History', 'history')) ->addToLeftGroup(new ToolbarButton('history', 'History', 'history', $contextData))
->addToRightGroup(new ToolbarButton('bookmark', 'Monitoring', 'monitoring')) ->addToRightGroup(new ToolbarButton('bookmark', 'Monitoring', 'monitoring', $contextData))
->addToRightGroup(new ToolbarButton('wrench', 'Edit', 'editManga')) ->addToRightGroup(new ToolbarButton('wrench', 'Edit', 'editManga', $contextData))
->addToRightGroup(new ToolbarButton('trash-can', 'Delete', 'deleteManga')) ->addToRightGroup(new ToolbarButton('trash-can', 'Delete', 'deleteManga', $contextData))
->addToRightGroup(new ToolbarDivider()) ->addToRightGroup(new ToolbarDivider())
->addToRightGroup(new ToolbarButton('chevron-down', 'Expand all', 'expandAll')); ->addToRightGroup(new ToolbarButton('chevron-down', 'Expand all', 'expandAll'));
} }

View File

@@ -8,7 +8,7 @@ use App\Manager\Toolbar\Element\ToolbarDropdown;
class MangaListToolbar extends Toolbar class MangaListToolbar extends Toolbar
{ {
public function __construct() public function __construct(array $contextData = [])
{ {
$this->addToLeftGroup(new ToolbarButton('arrows-rotate', 'Refresh', 'refreshMetadata')) $this->addToLeftGroup(new ToolbarButton('arrows-rotate', 'Refresh', 'refreshMetadata'))
->addToLeftGroup(new ToolbarButton('search', 'Search', 'searchLastChapter')) ->addToLeftGroup(new ToolbarButton('search', 'Search', 'searchLastChapter'))

View File

@@ -2,13 +2,24 @@
namespace App\Manager\Toolbar\Element; namespace App\Manager\Toolbar\Element;
use App\Manager\Toolbar\Element\AbstractToolbarElement;
class ToolbarButton extends AbstractToolbarElement class ToolbarButton extends AbstractToolbarElement
{ {
protected array $data;
public function __construct(string $icon, string $label, string $action, array $data = [])
{
parent::__construct($icon, $label, $action);
$this->data = $data;
}
public function getType(): string public function getType(): string
{ {
return 'button'; return 'button';
} }
public function getAdditionalProperties(): array
{
return ['data' => $this->data];
}
} }

View File

@@ -8,11 +8,11 @@ use App\Manager\Toolbar\Definition\Toolbar;
class ToolbarFactory class ToolbarFactory
{ {
public function createToolbar(string $type): Toolbar public function createToolbar(string $type, array $context = []): Toolbar
{ {
return match ($type) { return match ($type) {
'manga_list' => new MangaListToolbar(), 'manga_list' => new MangaListToolbar(),
'chapter_list' => new ChapterListToolbar(), 'chapter_list' => new ChapterListToolbar($context),
default => throw new \InvalidArgumentException("Unknown toolbar type: $type"), default => throw new \InvalidArgumentException("Unknown toolbar type: $type"),
}; };
} }

View File

@@ -2,13 +2,10 @@
namespace App\Message; namespace App\Message;
class DownloadChapter readonly class DownloadChapter
{ {
private int $chapterId; public function __construct(private int $chapterId)
public function __construct(int $chapterId)
{ {
$this->chapterId = $chapterId;
} }
public function getChapterId(): int public function getChapterId(): int

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Message;
readonly class RefreshMetadata
{
public function __construct(private int $mangaId)
{
}
public function getMangaId(): int
{
return $this->mangaId;
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\MessageHandler;
use App\Message\RefreshMetadata;
use App\Repository\MangaRepository;
use App\Service\MangadexProvider;
use App\Service\NotificationService;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
readonly class RefreshMetadataHandler
{
public function __construct(
private MangaRepository $mangaRepository,
private MangadexProvider $mangadexProvider,
private EntityManagerInterface $entityManager,
private NotificationService $notificationService
)
{
}
public function __invoke(RefreshMetadata $message): void
{
$manga = $this->mangaRepository->find($message->getMangaId());
if (!$manga) {
return;
}
$lastChapters = $this->mangadexProvider->addAllChaptersToManga($manga);
try {
foreach ($manga->getChapters() as $chapter) {
$this->entityManager->persist($chapter);
}
$this->entityManager->persist($manga);
$this->entityManager->flush();
} catch (\Exception $e) {
if ($e instanceof UniqueConstraintViolationException) {
$this->notificationService->sendUpdate(['status' => 'error', 'message' => 'An error occurred while refreshing ' . $manga->getTitle() . '.']);
return;
}
}
$this->notificationService->sendUpdate(['status' => 'success', 'message' => $manga->getTitle() . ' refreshed, ' . count($lastChapters) . ' new chapters added.']);
}
}

View File

@@ -18,18 +18,18 @@ readonly class MangadexProvider implements MetadataProviderInterface
public function search(?string $title): Collection public function search(?string $title): Collection
{ {
if($title === null) { if ($title === null) {
return new ArrayCollection(); return new ArrayCollection();
} }
try{ try {
$results = $this->client->get('/manga', [ $results = $this->client->get('/manga', [
'title' => $title, 'title' => $title,
'contentRating' => ['safe', 'suggestive'], 'contentRating' => ['safe', 'suggestive'],
'includes' => ['cover_art', 'author'], 'includes' => ['cover_art', 'author'],
'limit' => 25 'limit' => 25
]); ]);
}catch(\Exception $e){ } catch (\Exception $e) {
$this->notificationService->sendUpdate('notification', ['status' => 'error', 'message' => 'An error occurred while fetching data from Mangadex.']); $this->notificationService->sendUpdate('notification', ['status' => 'error', 'message' => 'An error occurred while fetching data from Mangadex.']);
return new ArrayCollection(); return new ArrayCollection();
} }
@@ -42,22 +42,21 @@ readonly class MangadexProvider implements MetadataProviderInterface
->setSlug($this->slugger->slug($result['attributes']['title']['en'])->lower()) ->setSlug($this->slugger->slug($result['attributes']['title']['en'])->lower())
->setDescription($result['attributes']['description']['fr'] ?? $result['attributes']['description']['en'] ?? '') ->setDescription($result['attributes']['description']['fr'] ?? $result['attributes']['description']['en'] ?? '')
->setPublicationYear($result['attributes']['year']) ->setPublicationYear($result['attributes']['year'])
->setStatus($result['attributes']['status']) ->setStatus($result['attributes']['status']);
;
$tags = []; $tags = [];
foreach($result['attributes']['tags'] as $tag){ foreach ($result['attributes']['tags'] as $tag) {
$tags[] = $tag['attributes']['name']['en']; $tags[] = $tag['attributes']['name']['en'];
} }
$mangas[count($mangas) - 1]->setGenres($tags); $mangas[count($mangas) - 1]->setGenres($tags);
foreach($result['relationships'] as $relationship) { foreach ($result['relationships'] as $relationship) {
if($relationship['type'] === 'author') { if ($relationship['type'] === 'author') {
$mangas[count($mangas) - 1]->setAuthor($relationship['attributes']['name']); $mangas[count($mangas) - 1]->setAuthor($relationship['attributes']['name']);
} }
if($relationship['type'] === 'cover_art') { if ($relationship['type'] === 'cover_art') {
$mangas[count($mangas) - 1]->setImageUrl('https://mangadex.org/covers/' . $result['id'] . '/' .$relationship['attributes']['fileName']); $mangas[count($mangas) - 1]->setImageUrl('https://mangadex.org/covers/' . $result['id'] . '/' . $relationship['attributes']['fileName']);
} }
} }
} }
@@ -68,7 +67,7 @@ readonly class MangadexProvider implements MetadataProviderInterface
'manga' => $test 'manga' => $test
]); ]);
foreach($mangas as $manga) { foreach ($mangas as $manga) {
$manga->setRating($ratings['statistics'][$manga->getExternalId()]['rating']['average']); $manga->setRating($ratings['statistics'][$manga->getExternalId()]['rating']['average']);
} }
@@ -77,12 +76,11 @@ readonly class MangadexProvider implements MetadataProviderInterface
public function getFeed(Manga $manga): array public function getFeed(Manga $manga): array
{ {
if($manga->getExternalId() === null) { if ($manga->getExternalId() === null) {
return []; return [];
} }
$chapters = []; $chapters = [];
$chapterEntities = [];
$page = 0; $page = 0;
do { do {
@@ -95,44 +93,40 @@ readonly class MangadexProvider implements MetadataProviderInterface
$page++; $page++;
} while (count($chapters) < $results['total']); } while (count($chapters) < $results['total']);
foreach($chapters as $result) { return $this->getChaptersFromFeed($chapters, $manga);
$chapterNumber = (float)$result['attributes']['chapter'];
// Utilisez la méthode exists de Doctrine pour vérifier si un chapitre avec le même numéro existe déjà
$chapterExists = $manga->getChapters()->exists(function($key, $existingChapter) use ($chapterNumber) {
return $existingChapter->getNumber() === $chapterNumber;
});
// Si le chapitre existe déjà, on skip
if ($chapterExists) {
continue;
} }
// Créez et ajoutez le nouveau chapitre public function getLastFeed(Manga $manga, int $limit = 100): array
$chapter = new Chapter(); {
$chapter->setNumber($chapterNumber) if ($manga->getExternalId() === null) {
->setTitle($result['attributes']['title']) return [];
->setVolume((int)$result['attributes']['volume'] ?? null)
->setExternalId($result['id'])
;
$chapterEntities[] = $chapter;
// $manga->addChapter($chapter);
} }
return $chapterEntities; $chapters = [];
try {
$results = $this->getFeedWithPagination($manga->getExternalId(), 0, $limit, 'desc');
if (isset($results['data'])) {
$chapters = $results['data'];
}
} catch (\Exception $e) {
$this->notificationService->sendUpdate(['status' => 'error', 'message' => 'An error occurred while fetching recent chapters from Mangadex.']);
return [];
} }
private function getFeedWithPagination(string $externalId, int $page): array return $this->getChaptersFromFeed($chapters, $manga);
}
private function getFeedWithPagination(string $externalId, int $page, int $limit = 500, string $order = 'asc'): array
{ {
try { try {
$response = $this->client->get('/manga/' . $externalId . '/feed', [ $response = $this->client->get('/manga/' . $externalId . '/feed', [
'limit' => 500, 'limit' => $limit,
'translatedLanguage' =>['en', 'fr'], 'translatedLanguage' => ['en', 'fr'],
'order' => ['chapter' => 'asc'], 'order' => ['chapter' => $order],
'offset' => $page * 500 'offset' => $page * $limit
]); ]);
}catch(\Exception $e){ } catch (\Exception $e) {
$this->notificationService->sendUpdate(['status' => 'error', 'message' => 'An error occurred while fetching data from Mangadex.']); $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'An error occurred while fetching data from Mangadex.']);
return []; return [];
} }
@@ -142,24 +136,24 @@ readonly class MangadexProvider implements MetadataProviderInterface
public function getMangaAggregate(Manga $manga): array public function getMangaAggregate(Manga $manga): array
{ {
if($manga->getExternalId() === null) { if ($manga->getExternalId() === null) {
return []; return [];
} }
try { try {
$response = $this->client->get('/manga/' . $manga->getExternalId() . '/aggregate'); $response = $this->client->get('/manga/' . $manga->getExternalId() . '/aggregate');
}catch(\Exception $e){ } catch (\Exception $e) {
// $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'An error occurred while fetching data from Mangadex.']); // $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'An error occurred while fetching data from Mangadex.']);
return []; return [];
} }
$chapterEntities = []; $chapterEntities = [];
if($response['result'] === 'ok'){ if ($response['result'] === 'ok') {
foreach($response['volumes'] as $volume){ foreach ($response['volumes'] as $volume) {
$volumeNumber = $volume['volume'] === 'none' ? 0 : (float) $volume['volume']; $volumeNumber = $volume['volume'] === 'none' ? 0 : (float)$volume['volume'];
foreach($volume['chapters'] as $chapter){ foreach ($volume['chapters'] as $chapter) {
$chapterEntity = new Chapter(); $chapterEntity = new Chapter();
$chapterEntity->setNumber((float) $chapter['chapter']) $chapterEntity->setNumber((float)$chapter['chapter'])
->setTitle('Chapter ' . $chapter['chapter']) ->setTitle('Chapter ' . $chapter['chapter'])
->setVolume($volumeNumber) ->setVolume($volumeNumber)
->setExternalId(''); ->setExternalId('');
@@ -171,4 +165,89 @@ readonly class MangadexProvider implements MetadataProviderInterface
} }
return $chapterEntities; return $chapterEntities;
} }
/**
* @param mixed $chapters
* @param Manga $manga
* @param array $chapterEntities
* @return array
*/
public function getChaptersFromFeed(mixed $chapters, Manga $manga): array
{
$chapterEntities = [];
$uniqueChapterNumbers = [];
foreach ($chapters as $result) {
$chapterNumber = (float)$result['attributes']['chapter'];
// Vérifiez si le chapitre existe déjà dans la base de données
$chapterExists = $manga->getChapters()->exists(function ($key, $existingChapter) use ($chapterNumber) {
return $existingChapter->getNumber() === $chapterNumber;
});
// Si le chapitre existe déjà dans la base de données ou dans notre nouvelle liste, on skip
if ($chapterExists || in_array($chapterNumber, $uniqueChapterNumbers)) {
continue;
}
// Créez et ajoutez le nouveau chapitre
$chapter = new Chapter();
$chapter->setNumber($chapterNumber)
->setTitle($result['attributes']['title'])
->setVolume((int)$result['attributes']['volume'] ?? null)
->setExternalId($result['id']);
$chapterEntities[] = $chapter;
$uniqueChapterNumbers[] = $chapterNumber;
}
// Trier les chapitres par numéro
usort($chapterEntities, function ($a, $b) {
return $a->getNumber() <=> $b->getNumber();
});
return $chapterEntities;
}
public function addAllChaptersToManga(Manga $manga): array
{
$mangaFeed = $this->getFeed($manga);
$mangaAggregate = $this->getMangaAggregate($manga);
$allChapters = array_merge($mangaFeed, $mangaAggregate);
if (empty($allChapters)) {
$this->notificationService->sendUpdate([
'status' => 'error',
'message' => 'No chapters found for this manga.'
]);
return [];
}
$mergedChapters = [];
foreach ($allChapters as $chapter) {
$number = $chapter->getNumber();
$existingChapter = $manga->getChapterByNumber($number);
if ($existingChapter) {
if ($existingChapter->getExternalId() !== $chapter->getExternalId() && is_null($existingChapter->getExternalId())) {
$this->updateChapter($existingChapter, $chapter);
$mergedChapters[$number] = $existingChapter;
}
} else {
// Add new chapter
$manga->addChapter($chapter);
$mergedChapters[$number] = $chapter;
}
}
return array_values($mergedChapters);
}
private function updateChapter(Chapter $existingChapter, Chapter $newChapter): void
{
$existingChapter->setVolume($newChapter->getVolume());
$existingChapter->setExternalId($newChapter->getExternalId());
}
} }

View File

@@ -3,10 +3,13 @@
namespace App\Twig\Components; namespace App\Twig\Components;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait; use Symfony\UX\LiveComponent\DefaultActionTrait;
#[AsLiveComponent] #[AsLiveComponent]
final class ToolBarButton final class ToolBarButton
{ {
use DefaultActionTrait; use DefaultActionTrait;
#[LiveProp (writable: true)]
public ?array $data = null;
} }

View File

@@ -1,10 +1,19 @@
{# templates/components/ToolbarButton.html.twig #} {# templates/components/ToolbarButton.html.twig #}
{% set buttonAttributes = {} %}
{% if data is defined and data is not empty %}
{% for key, value in data %}
{% set dataAttribute = 'data-' ~ key|replace({'_': '-'})|lower ~ '=' ~ value %}
{% set buttonAttributes = buttonAttributes|merge({dataAttribute}) %}
{% endfor %}
{% endif %}
<div {{ attributes }}> <div {{ attributes }}>
<button <button class="flex flex-col justify-around min-h-14 w-min ml-4 items-center text-white group"
class="flex flex-col justify-around min-h-14 w-min ml-4 items-center text-white group"
{% if action %} {% if action %}
{{ stimulus_action(controller|default('toolbar'), action) }} {{ stimulus_action('toolbar', action) }}
{% endif %} {% endif %}
{{ buttonAttributes|join(' ') }}
> >
<i class="fas fa-{{ icon }} text-xl group-hover:text-green-500"></i> <i class="fas fa-{{ icon }} text-xl group-hover:text-green-500"></i>
<span class="text-xs">{{ text }}</span> <span class="text-xs">{{ text }}</span>

View File

@@ -8,6 +8,7 @@
icon="{{ element.icon }}" icon="{{ element.icon }}"
text="{{ element.text }}" text="{{ element.text }}"
action="{{ element.action }}" action="{{ element.action }}"
data="{{ element.additionalProperties.data }}"
/> />
{% elseif element.type == 'divider' %} {% elseif element.type == 'divider' %}
<twig:Divider/> <twig:Divider/>
@@ -28,6 +29,7 @@
icon="{{ element.icon }}" icon="{{ element.icon }}"
text="{{ element.text }}" text="{{ element.text }}"
action="{{ element.action }}" action="{{ element.action }}"
data="{{ element.additionalProperties.data }}"
/> />
{% elseif element.type == 'divider' %} {% elseif element.type == 'divider' %}
<twig:Divider/> <twig:Divider/>