diff --git a/assets/app.js b/assets/app.js index 5725158..41640e5 100644 --- a/assets/app.js +++ b/assets/app.js @@ -20,6 +20,7 @@ import './styles/a2hs.css'; import './styles/analytics.css'; import './styles/modal.css'; import './styles/utilities.css'; +import './styles/landing.css'; console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉'); diff --git a/assets/controllers/sidebar_toggle_controller.js b/assets/controllers/sidebar_toggle_controller.js new file mode 100644 index 0000000..a1833e5 --- /dev/null +++ b/assets/controllers/sidebar_toggle_controller.js @@ -0,0 +1,115 @@ +import { Controller } from '@hotwired/stimulus'; + +/* + * Sidebar toggle controller + * Controls showing/hiding nav (#leftNav) and aside (#rightNav) on mobile viewports. + * Uses aria-controls attribute of clicked toggle buttons. + */ +export default class extends Controller { + static targets = [ ]; + + connect() { + this.mediaQuery = window.matchMedia('(min-width: 769px)'); + this.resizeListener = () => this.handleResize(); + this.keyListener = (e) => this.handleKeydown(e); + this.clickOutsideListener = (e) => this.handleDocumentClick(e); + this.mediaQuery.addEventListener('change', this.resizeListener); + document.addEventListener('keydown', this.keyListener); + document.addEventListener('click', this.clickOutsideListener); + this.handleResize(); + } + + disconnect() { + this.mediaQuery.removeEventListener('change', this.resizeListener); + document.removeEventListener('keydown', this.keyListener); + document.removeEventListener('click', this.clickOutsideListener); + } + + toggle(event) { + const controlId = event.currentTarget.getAttribute('aria-controls'); + const el = document.getElementById(controlId); + if (!el) return; + if (el.classList.contains('is-open')) { + this.closeElement(el); + } else { + this.openElement(el); + } + this.syncAria(controlId); + } + + close(event) { + // Close button inside a sidebar + const container = event.currentTarget.closest('nav, aside'); + if (container) { + this.closeElement(container); + this.syncAria(container.id); + } + } + + openElement(el) { + // Only apply overlay behavior on mobile + if (this.isDesktop()) return; // grid already shows them + el.classList.add('is-open'); + document.body.classList.add('no-scroll'); + } + + closeElement(el) { + el.classList.remove('is-open'); + if (!this.anyOpen()) { + document.body.classList.remove('no-scroll'); + } + } + + anyOpen() { + return !!document.querySelector('nav.is-open, aside.is-open'); + } + + syncAria(id) { + // Update any toggle buttons that control this id + const expanded = document.getElementById(id)?.classList.contains('is-open') || false; + document.querySelectorAll(`[aria-controls="${id}"]`).forEach(btn => { + btn.setAttribute('aria-expanded', expanded.toString()); + }); + } + + handleResize() { + if (this.isDesktop()) { + // Ensure both sidebars are visible in desktop layout + ['leftNav', 'rightNav'].forEach(id => { + const el = document.getElementById(id); + if (el) el.classList.remove('is-open'); + this.syncAria(id); + }); + document.body.classList.remove('no-scroll'); + } else { + // On mobile ensure aria-expanded is false unless explicitly opened + ['leftNav', 'rightNav'].forEach(id => this.syncAria(id)); + } + } + + handleKeydown(e) { + if (e.key === 'Escape') { + const open = document.querySelectorAll('nav.is-open, aside.is-open'); + if (open.length) { + open.forEach(el => this.closeElement(el)); + ['leftNav', 'rightNav'].forEach(id => this.syncAria(id)); + } + } + } + + handleDocumentClick(e) { + if (this.isDesktop()) return; // only needed mobile + const open = document.querySelectorAll('nav.is-open, aside.is-open'); + if (!open.length) return; + const inside = e.target.closest('nav, aside, .mobile-toggles'); + if (!inside) { + open.forEach(el => this.closeElement(el)); + ['leftNav', 'rightNav'].forEach(id => this.syncAria(id)); + } + } + + isDesktop() { + return this.mediaQuery.matches; + } +} + diff --git a/assets/styles/app.css b/assets/styles/app.css index 2a613cc..830ff33 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -27,7 +27,14 @@ h1 { h1.brand { font-family: var(--brand-font), serif; - font-size: 3.6rem; + color: var(--color-primary); + font-size: 3.2rem; +} + +@media screen and (max-width: 600px) { + h1.brand { + font-size: 2.3rem; + } } h1.brand a { @@ -38,6 +45,11 @@ h2 { font-size: 2.2rem; } +h2.brand { + font-family: var(--brand-font), serif; + color: var(--color-primary); +} + h3 { font-size: 2rem; } @@ -262,10 +274,19 @@ div:nth-child(odd) .featured-list { z-index: 1000; /* Ensure it stays on top */ display: flex; flex-direction: column; - justify-content: space-around; + justify-content: space-between; align-items: center; background-color: var(--color-bg); /* Black background */ - border-bottom: 1px solid var(--color-border); /* White bottom border */ + border-bottom: 1px solid var(--color-border); +} + +.header .container { + display: flex; + flex-direction: column; + align-items: start; + width: 100%; + max-width: 1200px; + padding: 0 20px; } .header__categories ul { diff --git a/assets/styles/button.css b/assets/styles/button.css index e943fc4..b2141bd 100644 --- a/assets/styles/button.css +++ b/assets/styles/button.css @@ -22,6 +22,12 @@ button:active, .btn:active { border-color: var(--color-text); } +a.btn, a.btn:hover, a.btn:active { + text-decoration: none; + display: inline-block; + text-align: center; +} + .btn.btn-secondary { color: var(--color-secondary); background-color: var(--color-bg); diff --git a/assets/styles/form.css b/assets/styles/form.css index ec29103..1527c20 100644 --- a/assets/styles/form.css +++ b/assets/styles/form.css @@ -1,28 +1,43 @@ -form, form > div { +form { display: flex; flex-direction: column; clear: both; margin-bottom: 1em; } -label { +form > div:not(.actions) { display: flex; - clear: both; + flex-direction: column; + margin-bottom: 1.5em; } -input { +label { display: flex; clear: both; } input, textarea, select { + display: block; + clear: both; +} + +input, textarea, select, .quill { background-color: var(--color-bg); color: var(--color-text); - border: 2px solid var(--color-border); - padding: 10px; + border: 1px solid var(--color-primary); border-radius: 0; /* Sharp edges */ } +input, textarea, select { + padding: 10px; +} + +textarea, input, select { + font-family: var(--font-family), sans-serif; + font-size: 1rem; + line-height: 1.5; +} + input:focus, textarea:focus, select:focus { border-color: var(--color-primary); outline: none; @@ -56,14 +71,6 @@ input:focus, textarea:focus, select:focus { flex-grow: 1; } -textarea, input { - font-family: var(--font-family), sans-serif; -} - -.quill { - border: 2px solid var(--color-border); -} - #editor { margin: 0; } @@ -72,3 +79,11 @@ button:disabled { opacity: 0.5; cursor: not-allowed; } + +fieldset { + border: none; +} + +.actions { + margin-bottom: 1.5em; +} diff --git a/assets/styles/landing.css b/assets/styles/landing.css new file mode 100644 index 0000000..00d4629 --- /dev/null +++ b/assets/styles/landing.css @@ -0,0 +1,72 @@ + +.center{ text-align: center; } + +/* Eyebrow + lede */ +.eyebrow{ + text-transform: uppercase; + letter-spacing: .12em; + font-size: .8rem; + color: var(--color-text-mid); + margin: 0 0 1rem; +} + +/* Hero split */ +.ln-hero{ + margin-top: 100px; + background: var(--color-bg); + color: var(--color-text); +} +.ln-hero__copy{ } +.ln-hero__frame{ display:flex; justify-content:center; } +.frame{ + width: min(560px, 40vw); + border-radius: 14px; + border: var(--border-hair); + background: linear-gradient(180deg, color-mix(in oklab, var(--color-bg-light) 80%, var(--color-bg)), var(--color-bg)); + overflow: hidden; +} + +/* Generic section shell */ +.ln-section{ position: relative; padding: 3.2rem 0; } + +/* Split layout for features */ +.ln-split{ + display: grid; gap: var(--gutter); + grid-template-columns: 280px 1fr; + align-items: start; +} +.ln-split__aside{ position: sticky; top: 24px; align-self: start; } +.ln-split__body .measure{ max-width: 70ch; } +.cta-row{ margin-top: .6rem; } + +/* Section palettes (alternating) */ +.ln-section--search{ + background: var(--color-accent-300); +} + +.ln-section--newsstand{ + background: color-mix(in oklab, var(--color-primary) 18%, var(--color-bg)); +} + +.ln-section--reader{ + background: var(--color-bg-light); +} +.ln-section--editor{ + background: var(--color-teal-400); +} +.ln-section--newsroom{ + background: color-mix(in oklab, var(--color-bg-light) 82%, var(--color-bg)); +} +.ln-section--marketplace{ + background: color-mix(in oklab, var(--color-primary) 18%, var(--color-bg)); +} +.ln-section--unfold{ + background: color-mix(in oklab, var(--color-accent-warm) 60%, var(--color-bg)); +} + + + +/* Motion */ +@media (prefers-reduced-motion: reduce){ + *{ transition: none !important; animation: none !important; } +} diff --git a/assets/styles/layout.css b/assets/styles/layout.css index a3b2d00..333a9a6 100644 --- a/assets/styles/layout.css +++ b/assets/styles/layout.css @@ -9,27 +9,40 @@ * - Footer (footer) **/ - - - /* Layout Container */ .layout { max-width: 100%; - width: 1200px; + width: 100%; margin: 0 auto; - display: flex; flex-grow: 1; + display: grid; + grid-template-columns: 200px auto 200px; +} + +nav, aside { + position: sticky; + top: 70px; } nav { - width: 21vw; - min-width: 150px; - max-width: 280px; - flex-shrink: 0; padding: 1em; overflow-y: auto; /* Ensure the menu is scrollable if content is too long */ } +header { + position: fixed; + width: 100vw; + min-height: 60px; + top: 0; + left: 0; +} + +.header__logo { + display: flex; + width: 100%; + margin-left: 10px; +} + nav ul { list-style-type: none; padding: 0; @@ -49,19 +62,6 @@ nav a:hover { text-decoration: none; } -header { - position: fixed; - width: 100vw; - top: 0; - left: 0; -} - -.header__logo { - display: flex; - width: 100%; - justify-content: center; -} - #progress-bar { position: absolute; left: 0; @@ -75,16 +75,33 @@ header { /* Main content */ main { + display: flex; + flex-direction: column; margin-top: 90px; flex-grow: 1; - padding: 1em; + padding: 0 1em; word-break: break-word; } +main.static { + margin-left: auto; + margin-right: auto; + max-width: 1000px; +} + +.w-container { + max-width: 1000px; + margin-left: auto; + margin-right: auto; + width: 100%; + display: flex; + flex-direction: column; + gap: 1rem; +} + .user-menu { position: fixed; top: 100px; - width: calc(21vw - 10px); min-width: 150px; max-width: 270px; } @@ -96,9 +113,10 @@ main { /* Right sidebar */ aside { - margin-top: 90px; - width: 300px; - padding: 1em; + display: flex; + flex-direction: column; + margin-top: 80px; + padding: 0 1em; } table { @@ -121,14 +139,93 @@ dt { margin-top: 10px; } +.mobile-toggles { + display: none; +} + +nav header, +aside header { + display: none; +} + /* Responsive adjustments */ @media (max-width: 768px) { + .layout { + grid-template-columns: auto; + } + + nav header, + aside header { + display: block; + } + nav, aside { display: none; /* Hide the sidebars on small screens */ } - main { - margin-top: 90px; - width: 100%; + + .mobile-toggles { + display: flex; + justify-content: space-between; + gap: .5rem; + position: sticky; + top: 61px; /* below main header */ + background: var(--color-bg); + z-index: 1050; + padding: .5rem 0; + } + + .mobile-toggles .toggle { + background: var(--color-primary); + color: var(--color-text-contrast); + border: none; + padding: .4rem .75rem; + cursor: pointer; + font: inherit; + } + + nav.is-open, aside.is-open { + display: block; + position: fixed; + top: 0; + bottom: 0; + width: 80%; + max-width: 260px; + background: var(--color-bg); + box-shadow: 0 0 12px rgba(0,0,0,.4); + overflow-y: auto; + z-index: 1200; + padding: 90px 1em 1em; /* space for global header height */ + animation: slideIn .25s ease; + } + + nav.is-open { left: 0; } + aside.is-open { right: 0; } + + body.no-scroll { overflow: hidden; } + + nav.is-open > header, aside.is-open > header { + position: absolute; + top: 0; + left: 0; + right: 0; + display: flex; + justify-content: flex-end; + padding: .5rem; + background: var(--color-bg); + border-bottom: 1px solid var(--color-border); + } + + nav.is-open button.close, aside.is-open button.close { + background: transparent; + border: none; + font-size: 1.2rem; + cursor: pointer; + color: var(--color-text); + } + + @keyframes slideIn { + from { transform: translateX(-15px); opacity: 0; } + to { transform: translateX(0); opacity: 1; } } } @@ -149,3 +246,8 @@ footer .footer-links { .search input { flex-grow: 1; } + +nav > header, aside > header { /* prevent global header fixed rules applying to nested headers */ + position: static; + width: auto; +} diff --git a/assets/styles/theme.css b/assets/styles/theme.css index 6fceda1..004c9fc 100644 --- a/assets/styles/theme.css +++ b/assets/styles/theme.css @@ -19,6 +19,19 @@ --heading-font: 'EB Garamond', serif; /* Set the font for headings */ --brand-font: 'Lobster', serif; /* A classic, refined branding font */ --brand-color: white; + + --color-accent: #8FCB7E; /* fresh moss (main accent) */ + --color-accent-strong: #B98BDC; /* lilac pop for headings/CTAs */ + --color-accent-teal: #78C8BD; /* teal for tags/pills */ + --color-accent-warm: #E1B574; /* warm highlight (badges/notes) */ + --color-accent-600: #7FBF70; + --color-accent-500: #8FCB7E; + --color-accent-400: #A5D692; + --color-accent-300: #BCE3A9; + --color-teal-500: #78C8BD; + --color-teal-400: #8ED5CC; + --color-lilac-500: #B98BDC; + --color-lilac-400: #C7A1E3; } [data-theme="light"] { diff --git a/assets/styles/utilities.css b/assets/styles/utilities.css index 797a9f8..b517a6f 100644 --- a/assets/styles/utilities.css +++ b/assets/styles/utilities.css @@ -8,7 +8,7 @@ .me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important} .mx-0{margin-left:0!important;margin-right:0!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-3{margin-left:1rem!important;margin-right:1rem!important}.mx-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-5{margin-left:3rem!important;margin-right:3rem!important} .my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important} - +.mx-auto{margin-left:auto!important;margin-right:auto!important} /* Display & layout */ .d-flex{display:flex!important;flex-direction:column} .d-inline{display:inline!important} @@ -16,6 +16,10 @@ .gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important} .flex-row{flex-direction:row} +.justify-content-between{justify-content:space-between!important} +.justify-content-center{justify-content:center!important} +.align-items-center{align-items:center!important} +.align-items-start{align-items:flex-start!important} /* Lists */ .list-unstyled{list-style:none;padding-left:0;margin:0} diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index 3f795d9..a461bab 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -1,5 +1,8 @@ twig: file_name_pattern: '*.twig' + globals: + project_npub: 'npub1ez09adke4vy8udk3y2skwst8q5chjgqzym9lpq4u58zf96zcl7kqyry2lz' + dev_npub: 'npub1636uujeewag8zv8593lcvdrwlymgqre6uax4anuq3y5qehqey05sl8qpl4' when@test: twig: diff --git a/config/packages/web_profiler.yaml b/config/packages/web_profiler.yaml index 8ef7507..e298f88 100644 --- a/config/packages/web_profiler.yaml +++ b/config/packages/web_profiler.yaml @@ -7,3 +7,4 @@ when@local: profiler: only_exceptions: false collect_serializer_data: true + collect: false diff --git a/src/Controller/Administration/MagazineAdminController.php b/src/Controller/Administration/MagazineAdminController.php index 6c8d34c..6e059d3 100644 --- a/src/Controller/Administration/MagazineAdminController.php +++ b/src/Controller/Administration/MagazineAdminController.php @@ -114,15 +114,6 @@ class MagazineAdminController extends AbstractController ]; } - // Sort alphabetically - usort($magazines, fn($a, $b) => strcmp($a['name'], $b['name'])); - foreach ($magazines as &$mag) { - usort($mag['categories'], fn($a, $b) => strcmp($a['name'], $b['name'])); - foreach ($mag['categories'] as &$cat) { - usort($cat['files'], fn($a, $b) => strcmp($a['name'], $b['name'])); - } - } - return $this->render('admin/magazines.html.twig', [ 'magazines' => $magazines, ]); diff --git a/src/Controller/DefaultController.php b/src/Controller/DefaultController.php index 802cc78..1f87470 100644 --- a/src/Controller/DefaultController.php +++ b/src/Controller/DefaultController.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Controller; +use Elastica\Collapse; use Elastica\Query; use Elastica\Query\Terms; use Exception; @@ -36,8 +37,11 @@ class DefaultController extends AbstractController $item->expiresAfter(13600); // about 4 hours // get latest articles $q = new Query(); - $q->setSize(50); + $q->setSize(12); $q->setSort(['createdAt' => ['order' => 'desc']]); + $col = new Collapse(); + $col->setFieldname('pubkey'); + $q->setCollapse($col); return $this->finder->find($q); }); @@ -46,12 +50,62 @@ class DefaultController extends AbstractController ]); } + /** + * @throws Exception + */ + #[Route('/newsstand', name: 'newsstand')] + public function newsstand(): Response + { + return $this->render('pages/newsstand.html.twig'); + } + + /** + * @throws Exception + */ + #[Route('/lists', name: 'lists')] + public function lists(): Response + { + return $this->render('pages/lists.html.twig'); + } + + /** + * @throws InvalidArgumentException + */ + #[Route('/latest', name: 'latest')] + public function latest() : Response + { + $cacheKey = 'home-latest-articles'; + $latest = $this->redisCache->get($cacheKey, function (ItemInterface $item) { + $item->expiresAfter(13600); // about 4 hours + // get latest articles + $q = new Query(); + $q->setSize(12); + $q->setSort(['createdAt' => ['order' => 'desc']]); + $col = new Collapse(); + $col->setFieldname('pubkey'); + $q->setCollapse($col); + return $this->finder->find($q); + }); + + return $this->render('home.html.twig', [ + 'latest' => $latest + ]); + } + + /** + * @throws InvalidArgumentException + */ + #[Route('/mag/{mag}', name: 'magazine-index')] + public function magIndex($mag) : Response + { + return new Response('Not implemented yet', 501); + } /** * @throws InvalidArgumentException */ - #[Route('/cat/{slug}', name: 'magazine-category')] - public function magCategory($slug, CacheInterface $redisCache, + #[Route('/mag/{mag}/cat/{slug}', name: 'magazine-category')] + public function magCategory($mag, $slug, CacheInterface $redisCache, FinderInterface $finder, LoggerInterface $logger): Response { @@ -163,6 +217,18 @@ class DefaultController extends AbstractController ]); } + /** + * @throws InvalidArgumentException + */ + #[Route('/list/{slug}', name: 'reading-list')] + public function readingList($slug, CacheInterface $redisCache, + FinderInterface $finder, + LoggerInterface $logger): Response + { + return new Response('Not implemented yet', 501); + } + + /** * OG Preview endpoint for URLs */ diff --git a/src/Controller/StaticController.php b/src/Controller/StaticController.php index 8bd4ce8..16d5a4b 100644 --- a/src/Controller/StaticController.php +++ b/src/Controller/StaticController.php @@ -39,4 +39,22 @@ class StaticController extends AbstractController { return $this->render('static/manifest.webmanifest.twig', [], new Response('', 200, ['Content-Type' => 'application/manifest+json'])); } + + #[Route('/landing', name: 'landing')] + public function landing(): Response + { + return $this->render('static/landing.html.twig'); + } + + #[Route('/unfold', name: 'unfold')] + public function unfold(): Response + { + return $this->render('static/unfold.html.twig'); + } + + #[Route('/journals', name: 'journals_index')] + public function journalsIndex(): Response + { + return $this->render('pages/journals.html.twig'); + } } diff --git a/src/Entity/Event.php b/src/Entity/Event.php index 196a701..f71568c 100644 --- a/src/Entity/Event.php +++ b/src/Entity/Event.php @@ -115,8 +115,8 @@ class Event public function getTitle(): ?string { foreach ($this->getTags() as $tag) { - if (array_key_first($tag) === 'title') { - return $tag['title']; + if ($tag[0] === 'title') { + return $tag[1]; } } return null; diff --git a/src/Twig/Components/Molecules/CategoryLink.php b/src/Twig/Components/Molecules/CategoryLink.php index eecc93d..7d4ecf4 100644 --- a/src/Twig/Components/Molecules/CategoryLink.php +++ b/src/Twig/Components/Molecules/CategoryLink.php @@ -10,6 +10,7 @@ final class CategoryLink { public string $title; public string $slug; + public ?string $mag = null; // magazine slug passed from parent (optional) public function __construct(private CacheInterface $redisCache) { @@ -18,21 +19,28 @@ final class CategoryLink public function mount($coordinate): void { if (key_exists(1, $coordinate)) { - $parts = explode(':', $coordinate[1]); - $this->slug = $parts[2]; - $cat = $this->redisCache->get('magazine-' . $parts[2], function (){ + $parts = explode(':', $coordinate[1], 3); + // Expect format kind:pubkey:slug + $this->slug = $parts[2] ?? ''; + $cat = $this->redisCache->get('magazine-' . $this->slug, function (){ return null; }); + if ($cat === null) { + $this->title = $this->slug ?: 'Category'; + return; + } + $tags = $cat->getTags(); $title = array_filter($tags, function($tag) { return ($tag[0] === 'title'); }); - $this->title = $title[array_key_first($title)][1]; + $this->title = $title[array_key_first($title)][1] ?? ($this->slug ?: 'Category'); } else { - dump($coordinate);die(); + $this->title = 'Category'; + $this->slug = ''; } } diff --git a/src/Twig/Components/Organisms/ZineList.php b/src/Twig/Components/Organisms/ZineList.php index a7104fa..6441ea2 100644 --- a/src/Twig/Components/Organisms/ZineList.php +++ b/src/Twig/Components/Organisms/ZineList.php @@ -12,26 +12,40 @@ use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; final class ZineList { public array $nzines = []; - public array $indices = []; public function __construct(private readonly EntityManagerInterface $entityManager) { } - public function mount(?array $nzines = null): void + public function mount(): void { - $this->nzines = $nzines ?? $this->entityManager->getRepository(Nzine::class)->findAll(); - if (count($this->nzines) > 0) { - // find indices for each nzine - foreach ($this->nzines as $zine) { - $ids = $this->entityManager->getRepository(Event::class)->findBy(['pubkey' => $zine->getNpub(), 'kind' => KindsEnum::PUBLICATION_INDEX]); - $id = array_filter($ids, function($k) use ($zine) { - return $k->getSlug() == $zine->getSlug(); - }); - if ($id) { - $this->indices[$zine->getNpub()] = array_pop($id); + + $nzines = $this->entityManager->getRepository(Event::class)->findBy(['kind' => KindsEnum::PUBLICATION_INDEX]); + + // filter, only keep type === magazine + $this->nzines = array_filter($nzines, function ($index) { + // look for tags + $tags = $index->getTags(); + $isMagType = false; + $isTopLevel = false; + foreach ($tags as $tag) { + // only if tag 'type' with value 'magazine' + if ($tag[0] === 'type' && $tag[1] === 'magazine') { + $isMagType = true; + } + // and only contains other indices: + // a tags with kind 30040 + if ($tag[0] === 'a' && $isTopLevel === false) { + // tag format: ['a', 'kind:pubkey:slug'] + $parts = explode(':', $tag[1]); + if ($parts[0] == (string)KindsEnum::PUBLICATION_INDEX->value) { + $isTopLevel = true; + } } } - } + return $isMagType && $isTopLevel; + }); + + } } diff --git a/templates/admin/analytics.html.twig b/templates/admin/analytics.html.twig index b8911eb..6f8fe1c 100644 --- a/templates/admin/analytics.html.twig +++ b/templates/admin/analytics.html.twig @@ -1,4 +1,4 @@ -{% extends 'base.html.twig' %} +{% extends 'layout.html.twig' %} {% block title %}Visitor Analytics{% endblock %} diff --git a/templates/admin/articles.html.twig b/templates/admin/articles.html.twig index 387474f..135b8f3 100644 --- a/templates/admin/articles.html.twig +++ b/templates/admin/articles.html.twig @@ -1,4 +1,4 @@ -{% extends 'base.html.twig' %} +{% extends 'layout.html.twig' %} {% block body %}

Latest 50 Articles

diff --git a/templates/admin/magazine_editor.html.twig b/templates/admin/magazine_editor.html.twig index cd9678b..5a90cd0 100644 --- a/templates/admin/magazine_editor.html.twig +++ b/templates/admin/magazine_editor.html.twig @@ -1,4 +1,4 @@ -{% extends 'base.html.twig' %} +{% extends 'layout.html.twig' %} {% block body %}

Edit Index: {{ title }}

diff --git a/templates/admin/magazines.html.twig b/templates/admin/magazines.html.twig index 7b47f02..a5295ef 100644 --- a/templates/admin/magazines.html.twig +++ b/templates/admin/magazines.html.twig @@ -1,4 +1,4 @@ -{% extends 'base.html.twig' %} +{% extends 'layout.html.twig' %} {% block body %}

Magazines

diff --git a/templates/admin/roles.html.twig b/templates/admin/roles.html.twig index 897ab14..fd67b02 100644 --- a/templates/admin/roles.html.twig +++ b/templates/admin/roles.html.twig @@ -1,4 +1,4 @@ -{% extends 'base.html.twig' %} +{% extends 'layout.html.twig' %} {% block body %}

{{ 'heading.roles'|trans }}

diff --git a/templates/admin/transactions.html.twig b/templates/admin/transactions.html.twig index 64f654d..e59325b 100644 --- a/templates/admin/transactions.html.twig +++ b/templates/admin/transactions.html.twig @@ -1,4 +1,4 @@ -{% extends 'base.html.twig' %} +{% extends 'layout.html.twig' %} {% block title %}Credit Transactions{% endblock %} diff --git a/templates/base.html.twig b/templates/base.html.twig index e86f81e..8b20d82 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -19,8 +19,7 @@ {% endblock %} - {% block stylesheets %} - {% endblock %} + {% block stylesheets %}{% endblock %} {% block javascripts %} {% block importmap %}{{ importmap('app') }}{% endblock %} {% endblock %} @@ -29,19 +28,7 @@ - -
- -
- {% block body %}{% endblock %} -
- -
+{% block layout %}{% endblock %}
-
diff --git a/templates/bundles/TwigBundle/Exception/error.html.twig b/templates/bundles/TwigBundle/Exception/error.html.twig index 9a1cffc..ea36417 100644 --- a/templates/bundles/TwigBundle/Exception/error.html.twig +++ b/templates/bundles/TwigBundle/Exception/error.html.twig @@ -1,4 +1,4 @@ -{% extends 'base.html.twig' %} +{% extends 'layout.html.twig' %} {% block body %}

Internal Server Error

diff --git a/templates/bundles/TwigBundle/Exception/error404.html.twig b/templates/bundles/TwigBundle/Exception/error404.html.twig index a25100b..50eb9b8 100644 --- a/templates/bundles/TwigBundle/Exception/error404.html.twig +++ b/templates/bundles/TwigBundle/Exception/error404.html.twig @@ -1,4 +1,4 @@ -{% extends 'base.html.twig' %} +{% extends 'layout.html.twig' %} {% block body %}

Page not found

diff --git a/templates/components/Header.html.twig b/templates/components/Header.html.twig index 523e73d..47397d3 100644 --- a/templates/components/Header.html.twig +++ b/templates/components/Header.html.twig @@ -1,6 +1,8 @@
-