blog posts
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
"doctrine/doctrine-migrations-bundle": "^3.4",
|
||||
"doctrine/orm": "^3.3",
|
||||
"league/commonmark": "^2.7",
|
||||
"league/html-to-markdown": "^5.1",
|
||||
"phpdocumentor/reflection-docblock": "^5.6",
|
||||
"phpstan/phpdoc-parser": "^2.1",
|
||||
"symfony/asset": "7.2.*",
|
||||
|
||||
91
composer.lock
generated
91
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "c3cbc37c53092d5fb23e7a7e21304c25",
|
||||
"content-hash": "194a6d80a9896ce73994dc1b6c059f1d",
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/semver",
|
||||
@@ -1635,6 +1635,95 @@
|
||||
],
|
||||
"time": "2022-12-11T20:36:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/html-to-markdown",
|
||||
"version": "5.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/html-to-markdown.git",
|
||||
"reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/0b4066eede55c48f38bcee4fb8f0aa85654390fd",
|
||||
"reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-xml": "*",
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mikehaertl/php-shellcommand": "^1.1.0",
|
||||
"phpstan/phpstan": "^1.8.8",
|
||||
"phpunit/phpunit": "^8.5 || ^9.2",
|
||||
"scrutinizer/ocular": "^1.6",
|
||||
"unleashedtech/php-coding-standard": "^2.7 || ^3.0",
|
||||
"vimeo/psalm": "^4.22 || ^5.0"
|
||||
},
|
||||
"bin": [
|
||||
"bin/html-to-markdown"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.2-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\HTMLToMarkdown\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Colin O'Dell",
|
||||
"email": "colinodell@gmail.com",
|
||||
"homepage": "https://www.colinodell.com",
|
||||
"role": "Lead Developer"
|
||||
},
|
||||
{
|
||||
"name": "Nick Cernis",
|
||||
"email": "nick@cern.is",
|
||||
"homepage": "http://modernnerd.net",
|
||||
"role": "Original Author"
|
||||
}
|
||||
],
|
||||
"description": "An HTML-to-markdown conversion helper for PHP",
|
||||
"homepage": "https://github.com/thephpleague/html-to-markdown",
|
||||
"keywords": [
|
||||
"html",
|
||||
"markdown"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/html-to-markdown/issues",
|
||||
"source": "https://github.com/thephpleague/html-to-markdown/tree/5.1.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.colinodell.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.paypal.me/colinpodell/10.00",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/colinodell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/league/html-to-markdown",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-12T21:21:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "3.9.0",
|
||||
|
||||
@@ -18,6 +18,7 @@ services:
|
||||
exclude:
|
||||
- '../src/DependencyInjection/'
|
||||
- '../src/Entity/'
|
||||
- '../src/Interface/'
|
||||
- '../src/Kernel.php'
|
||||
|
||||
# add more service definitions when explicit configuration is needed
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use DateTime;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
@@ -38,17 +39,21 @@ final class Version20250522212300 extends AbstractMigration
|
||||
$blogPostPath = 'scripts/blog-migrated/' . $slug . '.yaml';
|
||||
$blogPost = Yaml::parseFile($blogPostPath);
|
||||
|
||||
$publishedDate = $blogPost['pubDate'];
|
||||
$updatedDate = array_key_exists(array: $blogPost, key: 'updatedDate')
|
||||
? $blogPost['updatedDate']
|
||||
$publishedDateTime = DateTime::createFromFormat('U', strval($blogPost['pubDate']));
|
||||
$publishedDate = $publishedDateTime->format('Y-m-d');
|
||||
$updatedDateTime = array_key_exists(array: $blogPost, key: 'updatedDate')
|
||||
? DateTime::createFromFormat('U', strval($blogPost['updatedDate']))
|
||||
: null;
|
||||
$updatedDate = $updatedDateTime == null
|
||||
? 'NULL'
|
||||
: "'" . $updatedDateTime->format('Y-m-d') . "'";
|
||||
$title = str_replace("'", "''", $blogPost['title']);
|
||||
$description = str_replace("'", "''", $blogPost['description']);
|
||||
$content = str_replace("'", "''", $blogPost['content']);
|
||||
|
||||
$this->addSql(<<<SQL
|
||||
INSERT INTO blog_post(slug, published_date, updated_date, title, description, content)
|
||||
VALUES('$slug', '$publishedDate', '$updatedDate', '$title', '$description', '$content')
|
||||
VALUES('$slug', '$publishedDate', $updatedDate, '$title', '$description', '$content')
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
--colour-primary-fg: var(--colour-primary-90);
|
||||
--colour-primary-fg-accent: var(--colour-primary-80);
|
||||
--colour-primary-bg: var(--colour-primary-10);
|
||||
--colour-primary-fg-accent: var(--colour-primary-20);
|
||||
--colour-primary-bg-accent: var(--colour-primary-20);
|
||||
--colour-code-fg: var(--colour-primary-90);
|
||||
--colour-code-bg: var(--colour-primary-15);
|
||||
--colour-hyperlink: var(--colour-hyperlink-80);
|
||||
@@ -56,7 +56,7 @@
|
||||
--colour-primary-fg: var(--colour-primary-20);
|
||||
--colour-primary-fg-accent: var(--colour-primary-40);
|
||||
--colour-primary-bg: var(--colour-primary-95);
|
||||
--colour-primary-fg-accent: var(--colour-primary-90);
|
||||
--colour-primary-bg-accent: var(--colour-primary-90);
|
||||
--colour-hyperlink: var(--colour-hyperlink-40);
|
||||
}
|
||||
}
|
||||
@@ -162,13 +162,13 @@ h3, h4, h5, h6 {
|
||||
|
||||
/** Hyperlinks */
|
||||
|
||||
a:is(:link, :visited) {
|
||||
:is(:link, :visited) {
|
||||
color: var(--colour-hyperlink);
|
||||
text-decoration: underline;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
:hover {
|
||||
text-decoration: wavy;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
p:has(.dt-published, .dt-updated) {
|
||||
font-size: var(--font-size-sm);
|
||||
font-style: italic;
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
.p-summary {
|
||||
font-style: italic;
|
||||
font-size: var(--font-size-sm);
|
||||
@@ -1,23 +1,76 @@
|
||||
.skip-to {
|
||||
display: inline-block;
|
||||
margin-block-start: var(--spacing-block-xs);
|
||||
h2 {
|
||||
margin-block-start: var(--spacing-block-md);
|
||||
}
|
||||
|
||||
.skip-to ul {
|
||||
margin-block-start: var(--spacing-block-xs);
|
||||
}
|
||||
|
||||
.skip-to :is(ul, li) {
|
||||
display: inline-block;
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.skip-to li:last-of-type {
|
||||
list-style: none;
|
||||
h3 {
|
||||
margin-block-start: var(--spacing-block-sm);
|
||||
}
|
||||
|
||||
.h-entry {
|
||||
outline: 0.125rem solid var(--colour-primary-fg);
|
||||
outline-offset: 1rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--colour-primary-bg-accent);
|
||||
border: 0.125rem solid var(--colour-primary-fg);
|
||||
border-radius: 1rem;
|
||||
margin-inline: -1rem;
|
||||
margin-block-start: var(--spacing-block-sm);
|
||||
transition-duration: 250ms;
|
||||
|
||||
&:is(:hover, :focus, :focus-within, :focus-visible) {
|
||||
background-color: var(--colour-primary-fg-accent);
|
||||
color: var(--colour-primary-bg-accent);
|
||||
transition-duration: 100ms;
|
||||
}
|
||||
|
||||
+ .h-entry {
|
||||
margin-block-start: var(--spacing-block-xs);
|
||||
}
|
||||
|
||||
.u-url {
|
||||
color: inherit;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
padding: 1rem;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.p-name {
|
||||
margin-block-start: 0;
|
||||
|
||||
+ * {
|
||||
margin-block-start: var(--spacing-block-xs);
|
||||
}
|
||||
}
|
||||
|
||||
p:has(.dt-published, .dt-updated) {
|
||||
font-size: var(--font-size-sm);
|
||||
font-style: italic;
|
||||
|
||||
&:not(.p-name + *) {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.skip-to {
|
||||
display: inline-block;
|
||||
margin-block-start: var(--spacing-block-xs);
|
||||
|
||||
:is(ul, li) {
|
||||
display: inline-block;
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-block-start: var(--spacing-block-xs);
|
||||
}
|
||||
|
||||
li:last-of-type {
|
||||
list-style: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.h-entry {
|
||||
transition-property: background-color, color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\BlogPost;
|
||||
use App\Entity\Note;
|
||||
use App\Form\Type\NoteType;
|
||||
use DateTime;
|
||||
@@ -14,7 +15,25 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
use function Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
const MONTH_NAMES = [
|
||||
'01' => 'January',
|
||||
'02' => 'February',
|
||||
'03' => 'March',
|
||||
'04' => 'April',
|
||||
'05' => 'May',
|
||||
'06' => 'June',
|
||||
'07' => 'July',
|
||||
'08' => 'August',
|
||||
'09' => 'September',
|
||||
'10' => 'October',
|
||||
'11' => 'November',
|
||||
'12' => 'December',
|
||||
];
|
||||
|
||||
class GuiController extends AbstractController {
|
||||
/**
|
||||
* @return array<string,string>
|
||||
*/
|
||||
#[Route('/', name: 'index')]
|
||||
#[Template('/index.html.twig')]
|
||||
public function index(): array {
|
||||
@@ -23,7 +42,9 @@ class GuiController extends AbstractController {
|
||||
'description' => 'Joe Carstairs\' personal website',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
#[Route('/notes', name: 'notes')]
|
||||
#[Template('/notes.html.twig')]
|
||||
public function notes(
|
||||
@@ -43,93 +64,115 @@ class GuiController extends AbstractController {
|
||||
return [
|
||||
'title' => 'Joe Carstairs\' notes',
|
||||
'description' => 'Joe Carstairs\' notes',
|
||||
'isFeed' => True,
|
||||
'notes' => $notesByYearAndMonth,
|
||||
'years' => $years,
|
||||
'months' => $monthsByYear,
|
||||
'monthNames' => [
|
||||
'01' => 'January',
|
||||
'02' => 'February',
|
||||
'03' => 'March',
|
||||
'04' => 'April',
|
||||
'05' => 'May',
|
||||
'06' => 'June',
|
||||
'07' => 'July',
|
||||
'08' => 'August',
|
||||
'09' => 'September',
|
||||
'10' => 'October',
|
||||
'11' => 'November',
|
||||
'12' => 'December',
|
||||
],
|
||||
'monthNames' => MONTH_NAMES,
|
||||
];
|
||||
}
|
||||
/**
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
#[Route('/blog', name: 'blog_posts')]
|
||||
#[Template('/blog_posts.html.twig')]
|
||||
public function blogPosts(
|
||||
EntityManagerInterface $entityManager,
|
||||
): array {
|
||||
$posts = $entityManager->getRepository(BlogPost::class)->findAllOrderedBySlugDesc();
|
||||
$years = $this->getYears($posts);
|
||||
$postsByYear = $this->groupByYear($posts);
|
||||
$months = array_map(array: $years, callback: fn($year) => $this->getMonths($postsByYear[$year]));
|
||||
$monthsByYear = array_combine(keys: $years, values: $months);
|
||||
$groupByMonth = fn($posts) => $this->groupByMonth($posts);
|
||||
$postsByYearAndMonth = array_map(
|
||||
array: $postsByYear,
|
||||
callback: $groupByMonth,
|
||||
);
|
||||
|
||||
return [
|
||||
'title' => 'Joe Carstairs\' blog',
|
||||
'description' => 'Joe Carstairs\' blog',
|
||||
'isFeed' => True,
|
||||
'posts' => $postsByYearAndMonth,
|
||||
'years' => $years,
|
||||
'months' => $monthsByYear,
|
||||
'monthNames' => MONTH_NAMES,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $notes Note[]
|
||||
* @return Note[][]
|
||||
* @@template T of \FeedEntry
|
||||
* @param $entries T[]
|
||||
* @return T[][]
|
||||
*/
|
||||
function groupByYear(array $notes): array {
|
||||
$years = $this->getYears($notes);
|
||||
function groupByYear(array $entries): array {
|
||||
$years = $this->getYears($entries);
|
||||
$filterByYear = fn(string $year) =>
|
||||
fn($note) => ($note->getPublishedDate()->format('Y') == $year);
|
||||
$notesForYear = fn(string $year) =>
|
||||
array_filter(array: $notes, callback: $filterByYear($year));
|
||||
$notesByYear = array_map(
|
||||
fn($entry) => ($entry->getPublishedDate()->format('Y') == $year);
|
||||
$entriesForYear = fn(string $year) =>
|
||||
array_filter(array: $entries, callback: $filterByYear($year));
|
||||
$entriesByYear = array_map(
|
||||
array: $years,
|
||||
callback: $notesForYear,
|
||||
callback: $entriesForYear,
|
||||
);
|
||||
return array_combine(keys: $years, values: $notesByYear);
|
||||
return array_combine(keys: $years, values: $entriesByYear);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $notes Note[]
|
||||
* @return string[]
|
||||
*/
|
||||
function getYears(array $notes): array {
|
||||
$getYear = fn(Note $note): string => $note->getPublishedDate()->format('Y');
|
||||
$years = array_map(array: $notes, callback: $getYear);
|
||||
* @template T of \FeedEntry
|
||||
* @param $entries T[]
|
||||
* @return string[]
|
||||
*/
|
||||
function getYears(array $entries): array {
|
||||
$getYear = fn($note): string => $note->getPublishedDate()->format('Y');
|
||||
$years = array_map(array: $entries, callback: $getYear);
|
||||
$years = array_unique($years);
|
||||
arsort($years);
|
||||
return $years;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $notes Note[]
|
||||
* @return Note[]
|
||||
* @template T of \FeedEntry
|
||||
* @param $entries T[]
|
||||
* @return T[]
|
||||
*/
|
||||
function groupByMonth(array $notes): array {
|
||||
$months = $this->getMonths($notes);
|
||||
function groupByMonth(array $entries): array {
|
||||
$months = $this->getMonths($entries);
|
||||
$filterByMonth = fn(string $month) =>
|
||||
fn($note) => ($note->getPublishedDate()->format('m') == $month);
|
||||
$notesByMonth = array_map(
|
||||
fn($entry) => ($entry->getPublishedDate()->format('m') == $month);
|
||||
$entriesByMonth = array_map(
|
||||
array: $months,
|
||||
callback: fn(string $month) =>
|
||||
array_filter(array: $notes, callback: $filterByMonth($month)),
|
||||
array_filter(array: $entries, callback: $filterByMonth($month)),
|
||||
);
|
||||
return array_combine(keys: $months, values: $notesByMonth);
|
||||
return array_combine(keys: $months, values: $entriesByMonth);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $notes Note[]
|
||||
* @return string[]
|
||||
* @@template T of \FeedEntry
|
||||
* @param $entries T[]
|
||||
* @return string[]
|
||||
*/
|
||||
function getMonths(array $notes): array {
|
||||
$getMonth = fn(Note $note): string => $note->getPublishedDate()->format('m');
|
||||
$months = array_map(array: $notes, callback: $getMonth);
|
||||
function getMonths(array $entries): array {
|
||||
$getMonth = fn($entry) => $entry->getPublishedDate()->format('m');
|
||||
$months = array_map(array: $entries, callback: $getMonth);
|
||||
$months = array_unique($months);
|
||||
arsort($months);
|
||||
return $months;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $notes Note[]
|
||||
* @return Note[]
|
||||
* @template T of \FeedEntry
|
||||
* @param $entries T[]
|
||||
* @return T[]
|
||||
*/
|
||||
function sortBySlug(array $notes): array {
|
||||
$getSlug = fn(Note $note): string => $note->getSlug();
|
||||
$slugs = array_map(array: $notes, callback: $getSlug);
|
||||
$notesBySlug = array_combine(keys: $slugs, values: $notes);
|
||||
krsort($notesBySlug);
|
||||
return array_values($notesBySlug);
|
||||
function sortBySlug(array $entries): array {
|
||||
$getSlug = fn($entry): string => $entry->getSlug();
|
||||
$slugs = array_map(array: $entries, callback: $getSlug);
|
||||
$entriesBySlug = array_combine(keys: $slugs, values: $entries);
|
||||
krsort($entriesBySlug);
|
||||
return array_values($entriesBySlug);
|
||||
}
|
||||
|
||||
#[IsGranted('ROLE_EDITOR')]
|
||||
@@ -185,8 +228,30 @@ class GuiController extends AbstractController {
|
||||
return [
|
||||
'title' => 'Joe Carstairs\' notes',
|
||||
'description' => 'Joe Carstairs\' notes',
|
||||
'isFeedEntry' => True,
|
||||
'note' => $note,
|
||||
'slug' => $slug,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
#[Route('/blog/{slug}', name: 'blog_post')]
|
||||
#[Template('/blog_post.html.twig')]
|
||||
public function blogPost(
|
||||
EntityManagerInterface $entityManager,
|
||||
string $slug,
|
||||
): array {
|
||||
$repository = $entityManager->getRepository(BlogPost::class);
|
||||
$post = $repository->findOneBy(['slug' => $slug]);
|
||||
|
||||
return [
|
||||
'title' => $post->getTitle(),
|
||||
'description' => $post->getDescription(),
|
||||
'isFeedEntry' => True,
|
||||
'post' => $post,
|
||||
'slug' => $slug,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Interface\FeedEntry;
|
||||
use App\Repository\BlogPostRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: BlogPostRepository::class)]
|
||||
class BlogPost
|
||||
class BlogPost implements FeedEntry
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Interface\FeedEntry;
|
||||
use App\Repository\NoteRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: NoteRepository::class)]
|
||||
class Note
|
||||
class Note implements FeedEntry
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
|
||||
8
src/Interface/FeedEntry.php
Normal file
8
src/Interface/FeedEntry.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Interface;
|
||||
|
||||
interface FeedEntry {
|
||||
function getPublishedDate(): ?\DateTime;
|
||||
function getSlug(): ?string;
|
||||
}
|
||||
@@ -14,4 +14,15 @@ class BlogPostRepository extends ServiceEntityRepository
|
||||
public function __construct(ManagerRegistry $registry) {
|
||||
parent::__construct($registry, BlogPost::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BlogPost[]
|
||||
*/
|
||||
public function findAllOrderedBySlugDesc(): array
|
||||
{
|
||||
return $this->createQueryBuilder('n')
|
||||
->orderBy('n.slug', 'DESC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
<body>
|
||||
{{ include('components/_navbar.html.twig') }}
|
||||
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
{% block main %}
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
47
templates/blog_post.html.twig
Normal file
47
templates/blog_post.html.twig
Normal file
@@ -0,0 +1,47 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block content %}
|
||||
{% if post %}
|
||||
<article class="h-entry">
|
||||
<aside>
|
||||
<span>
|
||||
This is a blog post by
|
||||
<a class="p-author h-card" href="/">Joe Carstairs</a>.
|
||||
</span>
|
||||
<p>
|
||||
Published: <time class="dt-published">{{ post.publishedDate.format('c') }}
|
||||
</p>
|
||||
{% if post.updatedDate %}}
|
||||
<p>
|
||||
Updated: <time class="dt-updated">{{ post.updatedDate.format('c') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
<span hidden>
|
||||
<a class="u-url uid" href="{{ url('blog_post', { slug: post.slug }) }}">
|
||||
Permalink
|
||||
</a>
|
||||
</span>
|
||||
</aside>
|
||||
|
||||
<header>
|
||||
<h1 class="p-name">{% apply markdown_to_html %}{{ post.title }}{% endapply %}</h1>
|
||||
<p class="p-summary">
|
||||
{% apply markdown_to_html %}{{ post.description }}{% endapply %}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
|
||||
{% apply markdown_to_html %}
|
||||
<section class="e-content">
|
||||
{{ post.content }}
|
||||
</section>
|
||||
{% endapply %}
|
||||
</article>
|
||||
{% else %}
|
||||
<section>
|
||||
<h1>Post not found</h1>
|
||||
<p>I don't have a blog post '{{ slug }}'.</p>
|
||||
<p>Go back to <a href="{{ path('blog_posts') }}">Blog</a>.</p>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
81
templates/blog_posts.html.twig
Normal file
81
templates/blog_posts.html.twig
Normal file
@@ -0,0 +1,81 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<section class="h-feed">
|
||||
<h1 class="p-name">Joe Carstairs' blog</h1>
|
||||
|
||||
<p hidden>
|
||||
This blog is written by
|
||||
<a class="p-author h-card" href="{{ url('index') }}">
|
||||
Joe Carstairs
|
||||
</a>.
|
||||
</p>
|
||||
<p hidden>
|
||||
<a class="u-url" href="{{ url('blog_posts') }}">Permalink</a>
|
||||
</p>
|
||||
|
||||
{% if years %}
|
||||
<nav class="skip-to">
|
||||
Skip to:
|
||||
<ul>
|
||||
{% for year in years %}
|
||||
<li><a href="#{{ year }}">{{ year }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
{% for year in years %}
|
||||
<h2 id="{{ year }}">{{ year }}</h2>
|
||||
|
||||
<nav class="skip-to">
|
||||
Skip to:
|
||||
<ul>
|
||||
{% for month in months[year] %}
|
||||
<li><a href="#{{ year }}-{{ month }}">
|
||||
{{ monthNames[month] }} <span class="visually-hidden">{{ year }}</span>
|
||||
</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{% for month in months[year] %}
|
||||
<h3 id="{{ year }}-{{ month }}">{{ monthNames[month] }}</h3>
|
||||
|
||||
{% for post in posts[year][month] %}
|
||||
<section class="h-entry">
|
||||
<a class="u-url" href="{{ path('blog_post', { 'slug': post.slug }) }}">
|
||||
<h4 class="p-name">
|
||||
{{ post.title }}
|
||||
</h4>
|
||||
|
||||
<p>
|
||||
Added:
|
||||
<time class="dt-published" datetime="{{ post.publishedDate.format('c') }}">
|
||||
{{ post.publishedDate.format('j F Y') }}
|
||||
</time>
|
||||
</p>
|
||||
|
||||
{% if post.updatedDate %}
|
||||
<p>
|
||||
Updated:
|
||||
<time class="dt-updated" datetime="{{ post.updatedDate.format('c') }}">
|
||||
{{ post.updatedDate.format('j F Y') }}
|
||||
</time>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<section class="p-summary">
|
||||
{% apply markdown_to_html %}
|
||||
{{ post.description|markdown_to_html|striptags('<i><em><b><strong><sup><p>')|html_to_markdown }}
|
||||
{% endapply %}
|
||||
</section>
|
||||
</a>
|
||||
</section>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>I have no blog posts.</p>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -4,7 +4,7 @@
|
||||
<a href="/">Home</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/blog">Blog</a>
|
||||
<a href="{{ path('blog_posts') }}">Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('notes') }}">Notes</a>
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<link rel="stylesheet" href="/css/reset.css" />
|
||||
<link rel="stylesheet" href="/css/base.css" />
|
||||
<link rel="stylesheet" href="/css/hcard.css" />
|
||||
<link rel="stylesheet" href="/css/feed.css" />
|
||||
{% if isFeed|default(false) %}
|
||||
<link rel="stylesheet" href="/css/feed.css" />
|
||||
{% endif %}
|
||||
{% if isFeedEntry|default(false) %}
|
||||
<link rel="stylesheet" href="/css/feed-entry.css" />
|
||||
{% endif %}
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block content %}
|
||||
{% block main %}
|
||||
{% if note %}
|
||||
<section class="h-entry">
|
||||
<h1 class="p-name">Note {{ note.slug }}</h1>
|
||||
<a hidden class="p-author h-card" href="{{ url('index') }}">Joe Carstairs</a>
|
||||
<a hidden class="u-url" href="{{ url('note', { slug: note.slug }) }}">Permalink</a>
|
||||
<time class="dt-published">{{ note.publishedDate.format('c') }}
|
||||
{% apply markdown_to_html %}
|
||||
<section class="e-content">
|
||||
{{ note.content }}
|
||||
</section>
|
||||
{% endapply %}
|
||||
</section>
|
||||
<article class="h-entry">
|
||||
<header>
|
||||
<h1 class="p-name">Note {{ note.slug }}</h1>
|
||||
<p hidden><a class="p-author h-card" href="{{ url('index') }}">Joe Carstairs</a></p>
|
||||
<p hidden <a class="u-url" href="{{ url('note', { slug: note.slug }) }}">Permalink</a></p>
|
||||
<p>Added: <time class="dt-published">{{ note.publishedDate.format('c') }}</time></p>
|
||||
</header>
|
||||
|
||||
<section class="e-content">
|
||||
{{ note.content|markdown_to_html }}
|
||||
</section>
|
||||
</article>
|
||||
{% else %}
|
||||
<section>
|
||||
<h1>Note not found</h1>
|
||||
<p>I don't have a note '{{ slug }}'.</p>
|
||||
<p>Go back to <a href="/notes">Notes</a>.</p>
|
||||
</section>
|
||||
<main>
|
||||
<header>
|
||||
<h1>Note not found</h1>
|
||||
</header>
|
||||
<section>
|
||||
<p>I don't have a note '{{ slug }}'.</p>
|
||||
<p>Go back to <a href="/notes">Notes</a>.</p>
|
||||
</section>
|
||||
</main>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -14,14 +14,16 @@
|
||||
<a class="u-url" href="{{ url('notes') }}">Permalink</a>
|
||||
</p>
|
||||
|
||||
<nav class="skip-to">
|
||||
Skip to:
|
||||
<ul>
|
||||
{% for year in years %}
|
||||
<li><a href="#{{ year }}">{{ year }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% if years %}
|
||||
<nav class="skip-to">
|
||||
Skip to:
|
||||
<ul>
|
||||
{% for year in years %}
|
||||
<li><a href="#{{ year }}">{{ year }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
{% for year in years %}
|
||||
<h2 id="{{ year }}">{{ year }}</h2>
|
||||
@@ -42,18 +44,23 @@
|
||||
|
||||
{% for note in notes[year][month] %}
|
||||
<section class="h-entry">
|
||||
<h4><a class="p-name u-url" href="{{ url('note', { slug: note.slug }) }}">
|
||||
Note {{ note.slug }}
|
||||
</a></h4>
|
||||
<time class="dt-published" datetime="{{ note.publishedDate.format('c') }}">
|
||||
{{ note.publishedDate.format('j F Y') }}
|
||||
</time>
|
||||
<a class="u-url" href="{{ url('note', { slug: note.slug }) }}">
|
||||
<h4 class="p-name">
|
||||
Note {{ note.slug }}
|
||||
</h4>
|
||||
<p>
|
||||
Added:
|
||||
<time class="dt-published" datetime="{{ note.publishedDate.format('c') }}">
|
||||
{{ note.publishedDate.format('j F Y') }}
|
||||
</time>
|
||||
</p>
|
||||
|
||||
<section class="e-content">
|
||||
{% apply markdown_to_html %}
|
||||
{{ note.content }}
|
||||
{% endapply %}
|
||||
</section>
|
||||
<section class="e-content">
|
||||
{% apply markdown_to_html %}
|
||||
{{ note.content|markdown_to_html|striptags('<i><em><b><strong><sup><p>')|html_to_markdown }}
|
||||
{% endapply %}
|
||||
</section>
|
||||
</a>
|
||||
</section>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
Reference in New Issue
Block a user