Add import of feed entries

Basic import of entries from feeds.
Also make imported feeds accessible in frontend.
This commit is contained in:
Daniel Siepmann 2020-05-26 23:09:30 +02:00
parent 39ebf6bbc1
commit 8efad3e1fd
Signed by: Daniel Siepmann
GPG key ID: 33D6629915560EF4
29 changed files with 1355 additions and 252 deletions

View file

@ -1,8 +1,9 @@
body {
font-family: var(--font-family-default);
font-size: var(--font-size-base);
background: --color-background;
color: --color-foreground;
background: var(--color-background);
color: var(--color-foreground);
margin: 0;
padding: 0;
@ -12,6 +13,7 @@ h1 {
color: var(--color-blue-dark);
font-family: var(--font-family-headlines);
font-weight: 300;
font-size: var(--font-size-header-base);
letter-spacing: .14em;
word-spacing: .3em;
}
@ -19,6 +21,7 @@ h1 {
a {
color: var(--color-wihte-light);
text-decoration: underline;
line-height: var(--line-height);
&:active,
&:visited {

View file

@ -8,7 +8,9 @@
--color-white-light: #F7F7F7;
--color-white-dark: #B0B0B0;
--spacing-elements: 1rem;
--spacing-elements: 2rem;
--spacing-small-elements: 1rem;
--spacing-large-elements: 4rem;
--width-border-default: 0.2rem;
@ -16,4 +18,8 @@
--font-family-default: "DejaVu Sans Mono", "Menlo", "Consolas", monospace;
--font-family-headlines: "Montserrat", "Avenir", "Roboto", sans-serif;
--font-size-base: 1.5em;
--font-size-header-base: 2.5em;
--font-size-header-sub: 1.5em;
--line-height: 1.33em;
}

View file

@ -1,7 +1,8 @@
body > main > section {
padding: var(--spacing-elements);
header h1 {
margin: 0;
}
}
@import 'content/entries';

View file

@ -1,5 +1,5 @@
body > footer {
padding: var(--spacing-elements);
padding: var(--spacing-small-elements);
border: {
top: var(--color-blue-light) solid var(--width-border-default);

View file

@ -1,5 +1,5 @@
body > header {
padding: var(--spacing-elements);
padding: var(--spacing-small-elements);
h1 {
margin: 0;

View file

@ -5,6 +5,6 @@ body > main > nav {
a {
display: block;
padding: var(--spacing-elements);
padding: var(--spacing-small-elements);
}
}

View file

@ -0,0 +1,49 @@
article {
display: grid;
grid-template-columns: 50em var(--width-sidebar-max);
column-gap: var(--spacing-elements);
margin-top: var(--spacing-large-elements);
#layout-entryDetail & {
margin-top: 0;
}
h1 {
grid-column: 1 / 3;
margin-bottom: 0;
font-size: var(--font-size-header-sub);
#layout-entryDetail & {
margin-top: 0;
font-size: var(--font-size-header-base);
}
}
main {
margin-top: var(--spacing-elements);
line-height: var(--line-height);
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: calc(var(--font-size-header-sub) - 0.5);
}
}
nav {
grid-row: 2 / 4;
grid-column: 2;
a {
display: block;
margin: {
bottom: var(--spacing-small-elements);
}
}
}
}

View file

@ -6,6 +6,7 @@
"ext-ctype": "*",
"ext-iconv": "*",
"sensio/framework-extra-bundle": "^5.5",
"simplepie/simplepie": "^1.5",
"symfony/asset": "5.0.*",
"symfony/console": "5.0.*",
"symfony/dotenv": "5.0.*",
@ -14,10 +15,13 @@
"symfony/orm-pack": "^1.0",
"symfony/test-pack": "^1.0",
"symfony/twig-pack": "^1.0",
"symfony/yaml": "5.0.*"
"symfony/yaml": "5.0.*",
"twig/intl-extra": "^3.0",
"twig/string-extra": "^3.0"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3",
"symfony/debug-bundle": "5.0.*",
"symfony/maker-bundle": "^1.18",
"symfony/profiler-pack": "^1.0"
},

876
composer.lock generated
View file

@ -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": "137126b022b07b543848195132ab15ea",
"content-hash": "60b76191cb1dfc83ea8523bd130cf5e3",
"packages": [
{
"name": "doctrine/annotations",
@ -1702,6 +1702,73 @@
],
"time": "2020-05-06T12:12:33+00:00"
},
{
"name": "simplepie/simplepie",
"version": "1.5.5",
"source": {
"type": "git",
"url": "https://github.com/simplepie/simplepie.git",
"reference": "ae49e2201b6da9c808e5dac437aca356a11831b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/simplepie/simplepie/zipball/ae49e2201b6da9c808e5dac437aca356a11831b4",
"reference": "ae49e2201b6da9c808e5dac437aca356a11831b4",
"shasum": ""
},
"require": {
"ext-pcre": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"php": ">=5.6.0"
},
"require-dev": {
"phpunit/phpunit": "~5.4.3 || ~6.5"
},
"suggest": {
"ext-curl": "",
"ext-iconv": "",
"ext-intl": "",
"ext-mbstring": "",
"mf2/mf2": "Microformat module that allows for parsing HTML for microformats"
},
"type": "library",
"autoload": {
"psr-0": {
"SimplePie": "library"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Ryan Parman",
"homepage": "http://ryanparman.com/",
"role": "Creator, alumnus developer"
},
{
"name": "Sam Sneddon",
"homepage": "https://gsnedders.com/",
"role": "Alumnus developer"
},
{
"name": "Ryan McCue",
"email": "me@ryanmccue.info",
"homepage": "http://ryanmccue.info/",
"role": "Developer"
}
],
"description": "A simple Atom/RSS parsing library for PHP",
"homepage": "http://simplepie.org/",
"keywords": [
"atom",
"feeds",
"rss"
],
"time": "2020-05-01T12:23:14+00:00"
},
{
"name": "symfony/asset",
"version": "v5.0.8",
@ -3299,6 +3366,95 @@
],
"time": "2020-04-28T18:53:25+00:00"
},
{
"name": "symfony/intl",
"version": "v5.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/intl.git",
"reference": "dc50ad5039ac685ca87306a346dc119cacdfea25"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/intl/zipball/dc50ad5039ac685ca87306a346dc119cacdfea25",
"reference": "dc50ad5039ac685ca87306a346dc119cacdfea25",
"shasum": ""
},
"require": {
"php": "^7.2.5",
"symfony/polyfill-intl-icu": "~1.0"
},
"require-dev": {
"symfony/filesystem": "^4.4|^5.0"
},
"suggest": {
"ext-intl": "to use the component with locales other than \"en\""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Intl\\": ""
},
"classmap": [
"Resources/stubs"
],
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
},
{
"name": "Eriksen Costa",
"email": "eriksen.costa@infranology.com.br"
},
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A PHP replacement layer for the C intl extension that includes additional data from the ICU library.",
"homepage": "https://symfony.com",
"keywords": [
"i18n",
"icu",
"internationalization",
"intl",
"l10n",
"localization"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-04-12T14:40:17+00:00"
},
{
"name": "symfony/mime",
"version": "v5.0.8",
@ -3481,6 +3637,152 @@
],
"time": "2020-04-28T17:58:55+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.17.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "e094b0770f7833fdf257e6ba4775be4e258230b2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e094b0770f7833fdf257e6ba4775be4e258230b2",
"reference": "e094b0770f7833fdf257e6ba4775be4e258230b2",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.17-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Grapheme\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's grapheme_* functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"grapheme",
"intl",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-05-12T16:47:27+00:00"
},
{
"name": "symfony/polyfill-intl-icu",
"version": "v1.17.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-icu.git",
"reference": "4ef3923e4a86e1b6ef72d42be59dbf7d33a685e3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/4ef3923e4a86e1b6ef72d42be59dbf7d33a685e3",
"reference": "4ef3923e4a86e1b6ef72d42be59dbf7d33a685e3",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"symfony/intl": "~2.3|~3.0|~4.0|~5.0"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.17-dev"
}
},
"autoload": {
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's ICU-related data and classes",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"icu",
"intl",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-05-12T16:14:59+00:00"
},
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.17.0",
@ -3557,6 +3859,83 @@
],
"time": "2020-05-12T16:47:27+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.17.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "1357b1d168eb7f68ad6a134838e46b0b159444a9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/1357b1d168eb7f68ad6a134838e46b0b159444a9",
"reference": "1357b1d168eb7f68ad6a134838e46b0b159444a9",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.17-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-05-12T16:14:59+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.17.0",
@ -3914,6 +4293,84 @@
],
"time": "2020-03-27T16:56:45+00:00"
},
{
"name": "symfony/string",
"version": "v5.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "48a2f4b3597514e6c885c0ddb22d3bbdb60517d0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/48a2f4b3597514e6c885c0ddb22d3bbdb60517d0",
"reference": "48a2f4b3597514e6c885c0ddb22d3bbdb60517d0",
"shasum": ""
},
"require": {
"php": "^7.2.5",
"symfony/polyfill-intl-grapheme": "~1.0",
"symfony/polyfill-intl-normalizer": "~1.0",
"symfony/polyfill-mbstring": "~1.0",
"symfony/translation-contracts": "^1.1|^2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\String\\": ""
},
"files": [
"Resources/functions.php"
],
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony String component",
"homepage": "https://symfony.com",
"keywords": [
"grapheme",
"i18n",
"string",
"unicode",
"utf-8",
"utf8"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-03-16T13:02:39+00:00"
},
{
"name": "symfony/test-pack",
"version": "v1.0.6",
@ -4526,6 +4983,114 @@
],
"time": "2020-01-01T17:11:09+00:00"
},
{
"name": "twig/intl-extra",
"version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/twigphp/intl-extra.git",
"reference": "55d68a5836f40d62695488b4bdbc71fa71f52574"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/intl-extra/zipball/55d68a5836f40d62695488b4bdbc71fa71f52574",
"reference": "55d68a5836f40d62695488b4bdbc71fa71f52574",
"shasum": ""
},
"require": {
"php": "^7.1.3",
"symfony/intl": "^4.3|^5.0",
"twig/twig": "^2.4|^3.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^4.4|^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Twig\\Extra\\Intl\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
}
],
"description": "A Twig extension for Intl",
"homepage": "https://twig.symfony.com",
"keywords": [
"intl",
"twig"
],
"time": "2020-02-11T05:45:44+00:00"
},
{
"name": "twig/string-extra",
"version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/twigphp/string-extra.git",
"reference": "9905d4410f1214df183fbb1a5e7848c560fdd551"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/string-extra/zipball/9905d4410f1214df183fbb1a5e7848c560fdd551",
"reference": "9905d4410f1214df183fbb1a5e7848c560fdd551",
"shasum": ""
},
"require": {
"php": "^7.2.5",
"symfony/string": "^5.0",
"twig/twig": "^2.4|^3.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^4.4|^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.12-dev"
}
},
"autoload": {
"psr-4": {
"Twig\\Extra\\String\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
}
],
"description": "A Twig extension for Symfony String",
"homepage": "https://twig.symfony.com",
"keywords": [
"html",
"string",
"twig",
"unicode"
],
"time": "2020-02-06T15:26:33+00:00"
},
{
"name": "twig/twig",
"version": "v3.0.3",
@ -4915,6 +5480,86 @@
],
"time": "2020-04-10T16:34:50+00:00"
},
{
"name": "symfony/debug-bundle",
"version": "v5.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug-bundle.git",
"reference": "3e11ad42d31b4d996c9715a69e988f6a52a70c9d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/debug-bundle/zipball/3e11ad42d31b4d996c9715a69e988f6a52a70c9d",
"reference": "3e11ad42d31b4d996c9715a69e988f6a52a70c9d",
"shasum": ""
},
"require": {
"ext-xml": "*",
"php": "^7.2.5",
"symfony/http-kernel": "^4.4|^5.0",
"symfony/twig-bridge": "^4.4|^5.0",
"symfony/var-dumper": "^4.4|^5.0"
},
"conflict": {
"symfony/config": "<4.4",
"symfony/dependency-injection": "<4.4"
},
"require-dev": {
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/web-profiler-bundle": "^4.4|^5.0"
},
"suggest": {
"symfony/config": "For service container configuration",
"symfony/dependency-injection": "For using as a service from the container"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Bundle\\DebugBundle\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony DebugBundle",
"homepage": "https://symfony.com",
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-03-27T16:56:45+00:00"
},
{
"name": "symfony/maker-bundle",
"version": "v1.18.0",
@ -4997,157 +5642,6 @@
],
"time": "2020-05-15T18:51:23+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.17.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "e094b0770f7833fdf257e6ba4775be4e258230b2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e094b0770f7833fdf257e6ba4775be4e258230b2",
"reference": "e094b0770f7833fdf257e6ba4775be4e258230b2",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.17-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Grapheme\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's grapheme_* functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"grapheme",
"intl",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-05-12T16:47:27+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.17.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "1357b1d168eb7f68ad6a134838e46b0b159444a9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/1357b1d168eb7f68ad6a134838e46b0b159444a9",
"reference": "1357b1d168eb7f68ad6a134838e46b0b159444a9",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.17-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-05-12T16:14:59+00:00"
},
{
"name": "symfony/profiler-pack",
"version": "v1.0.4",
@ -5176,84 +5670,6 @@
"description": "A pack for the Symfony web profiler",
"time": "2018-12-10T12:11:44+00:00"
},
{
"name": "symfony/string",
"version": "v5.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "48a2f4b3597514e6c885c0ddb22d3bbdb60517d0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/48a2f4b3597514e6c885c0ddb22d3bbdb60517d0",
"reference": "48a2f4b3597514e6c885c0ddb22d3bbdb60517d0",
"shasum": ""
},
"require": {
"php": "^7.2.5",
"symfony/polyfill-intl-grapheme": "~1.0",
"symfony/polyfill-intl-normalizer": "~1.0",
"symfony/polyfill-mbstring": "~1.0",
"symfony/translation-contracts": "^1.1|^2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\String\\": ""
},
"files": [
"Resources/functions.php"
],
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony String component",
"homepage": "https://symfony.com",
"keywords": [
"grapheme",
"i18n",
"string",
"unicode",
"utf-8",
"utf8"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-03-16T13:02:39+00:00"
},
{
"name": "symfony/web-profiler-bundle",
"version": "v5.0.8",

View file

@ -10,4 +10,5 @@ return [
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
];

View file

@ -0,0 +1,4 @@
debug:
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
# See the "server:dump" command to start a new server.
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"

View file

@ -12,3 +12,7 @@ bucket:
feed:
path: /feed/{slug}
controller: App\Controller\FeedController::show
entry:
path: /entry/{slug}
controller: App\Controller\EntryController::show

View file

@ -4,6 +4,8 @@
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
# app.feedFetching.cacheDuration: 3600 # in seconds
app.feedFetching.cacheDuration: 604800 # in seconds, 1 week
services:
# default configuration for services in *this* file
@ -23,11 +25,23 @@ services:
resource: '../src/Controller'
tags: ['controller.service_arguments']
App\Service\FeedParserFactory:
arguments:
$cacheDuration: '%app.feedFetching.cacheDuration%'
$cacheLocation: '%kernel.cache_dir%/feedFetching'
# Commands
App\Command\ImportOpmlfileCommand:
tags:
- name: 'console.command'
command: 'app:import:opmlfile'
App\Command\ImportFeedEntriesCommand:
tags:
- name: 'console.command'
command: 'app:import:feedentries'
# Change existing aliases
slugger:
class: App\Service\Slugger

View file

@ -7,16 +7,23 @@
--color-black-dark: #000000;
--color-white-light: #F7F7F7;
--color-white-dark: #B0B0B0;
--spacing-elements: 1rem;
--spacing-elements: 2rem;
--spacing-small-elements: 1rem;
--spacing-large-elements: 4rem;
--width-border-default: 0.2rem;
--width-sidebar-max: 15rem;
--font-family-default: "DejaVu Sans Mono", "Menlo", "Consolas", monospace;
--font-family-headlines: "Montserrat", "Avenir", "Roboto", sans-serif; }
--font-family-headlines: "Montserrat", "Avenir", "Roboto", sans-serif;
--font-size-base: 1.5em;
--font-size-header-base: 2.5em;
--font-size-header-sub: 1.5em;
--line-height: 1.33em; }
body {
font-family: var(--font-family-default);
background: --color-background;
color: --color-foreground;
font-size: var(--font-size-base);
background: var(--color-background);
color: var(--color-foreground);
margin: 0;
padding: 0; }
@ -24,12 +31,14 @@ h1 {
color: var(--color-blue-dark);
font-family: var(--font-family-headlines);
font-weight: 300;
font-size: var(--font-size-header-base);
letter-spacing: .14em;
word-spacing: .3em; }
a {
color: var(--color-wihte-light);
text-decoration: underline; }
text-decoration: underline;
line-height: var(--line-height); }
a:active, a:visited {
color: var(--color-wihte-light); }
a:hover {
@ -37,7 +46,7 @@ a {
background: var(--color-blue-light); }
body > header {
padding: var(--spacing-elements);
padding: var(--spacing-small-elements);
border-bottom: var(--color-blue-light) solid var(--width-border-default); }
body > header h1 {
margin: 0; }
@ -46,15 +55,47 @@ body > main > nav {
border-right: var(--color-blue-light) solid var(--width-border-default); }
body > main > nav a {
display: block;
padding: var(--spacing-elements); }
padding: var(--spacing-small-elements); }
body > main > section {
padding: var(--spacing-elements); }
body > main > section header h1 {
margin: 0; }
article {
display: grid;
grid-template-columns: 50em var(--width-sidebar-max);
-moz-column-gap: var(--spacing-elements);
column-gap: var(--spacing-elements);
margin-top: var(--spacing-large-elements); }
#layout-entryDetail article {
margin-top: 0; }
article h1 {
grid-column: 1 / 3;
margin-bottom: 0;
font-size: var(--font-size-header-sub); }
#layout-entryDetail article h1 {
margin-top: 0;
font-size: var(--font-size-header-base); }
article main {
margin-top: var(--spacing-elements);
line-height: var(--line-height); }
article main h1,
article main h2,
article main h3,
article main h4,
article main h5,
article main h6 {
font-size: calc(var(--font-size-header-sub) - 0.5); }
article nav {
grid-row: 2 / 4;
grid-column: 2; }
article nav a {
display: block;
margin-bottom: var(--spacing-small-elements); }
body > footer {
padding: var(--spacing-elements);
padding: var(--spacing-small-elements);
border-top: var(--color-blue-light) solid var(--width-border-default); }
body > main {

View file

@ -0,0 +1,45 @@
<?php
namespace App\Command;
use App\Repository\FeedRepository;
use App\Service\FeedEntriesImporter;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ImportFeedEntriesCommand extends Command
{
/**
* @var FeedEntriesImporter
*/
private $importer;
/**
* @var FeedRepository
*/
private $repository;
public function __construct(
FeedEntriesImporter $importer,
FeedRepository $repository
) {
parent::__construct();
$this->importer = $importer;
$this->repository = $repository;
}
protected function configure()
{
$this->setDescription('Imports new entries from all known feeds.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
foreach ($this->repository->findAll() as $feed) {
$this->importer->importEntries($feed);
}
return 0;
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace App\Controller;
use App\Entity\Entry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class EntryController extends AbstractController
{
public function show(Entry $entry)
{
return $this->render('entry/show.html.twig', [
'entry' => $entry,
]);
}
}

136
src/Entity/Entry.php Normal file
View file

@ -0,0 +1,136 @@
<?php
namespace App\Entity;
use App\Repository\EntryRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=EntryRepository::class)
* @ORM\Table(uniqueConstraints={@ORM\UniqueConstraint(name="entry_routing", columns={"slug"})})
*/
class Entry
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=Feed::class, inversedBy="entries")
*/
private $feed;
/**
* @ORM\Column(type="string", length=255)
*/
private $publicId;
/**
* @ORM\Column(type="datetime_immutable")
*/
private $published;
/**
* @ORM\Column(type="string", length=255)
*/
private $name;
/**
* @ORM\Column(type="string", length=255)
*/
private $slug;
/**
* @ORM\Column(type="string", length=255)
*/
private $url;
/**
* @ORM\Column(type="string", length=255)
*/
private $category;
/**
* @ORM\Column(type="text")
*/
private $content;
/**
* @ORM\Column(type="boolean")
*/
private $read = false;
public function __construct(
Feed $feed,
string $publicId,
\DateTimeImmutable $published,
string $name,
string $slug,
string $url,
string $category,
string $content
) {
$this->feed = $feed;
$this->publicId = $publicId;
$this->published = $published;
$this->name = $name;
$this->slug = $slug;
$this->url = $url;
$this->category = $category;
$this->content = $content;
}
public function getId(): int
{
return $this->id;
}
public function getFeed(): Feed
{
return $this->feed;
}
public function getPublicId(): string
{
return $this->publicId;
}
public function getPublished(): \DateTimeImmutable
{
return $this->published;
}
public function getName(): string
{
return $this->name;
}
public function getSlug(): string
{
return $this->slug;
}
public function getUrl(): string
{
return $this->url;
}
public function getCategory(): string
{
return $this->category;
}
public function getContent(): string
{
return $this->content;
}
// TODO: Rename into is / was ?
public function getRead(): bool
{
return $this->read;
}
}

View file

@ -3,6 +3,8 @@
namespace App\Entity;
use App\Repository\FeedRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
@ -43,6 +45,12 @@ class Feed
*/
private $bucket;
/**
* @ORM\OneToMany(targetEntity=Entry::class, mappedBy="feed")
* @ORM\OrderBy({"published" = "DESC"})
*/
private $entries;
public function __construct(
string $name,
string $slug,
@ -55,6 +63,7 @@ class Feed
$this->url = $url;
$this->bucket = $bucket;
$this->htmlUrl = $htmlUrl;
$this->entries = new ArrayCollection();
}
public function getId(): int
@ -86,4 +95,9 @@ class Feed
{
return $this->htmlUrl;
}
public function getEntries(): Collection
{
return $this->entries;
}
}

View file

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20200528222741 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.');
$this->addSql('CREATE TABLE entry (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, feed_id INTEGER DEFAULT NULL, public_id VARCHAR(255) NOT NULL, published DATETIME NOT NULL --(DC2Type:datetime_immutable)
, name VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, category VARCHAR(255) NOT NULL, content CLOB NOT NULL, read BOOLEAN NOT NULL)');
$this->addSql('CREATE INDEX IDX_2B219D7051A5BC03 ON entry (feed_id)');
$this->addSql('CREATE UNIQUE INDEX entry_routing ON entry (slug)');
$this->addSql('DROP INDEX feed_routing');
$this->addSql('DROP INDEX IDX_234044AB84CE584D');
$this->addSql('CREATE TEMPORARY TABLE __temp__feed AS SELECT id, bucket_id, name, slug, url, html_url FROM feed');
$this->addSql('DROP TABLE feed');
$this->addSql('CREATE TABLE feed (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, bucket_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL COLLATE BINARY, slug VARCHAR(255) NOT NULL COLLATE BINARY, url VARCHAR(255) NOT NULL COLLATE BINARY, html_url VARCHAR(255) NOT NULL COLLATE BINARY, CONSTRAINT FK_234044AB84CE584D FOREIGN KEY (bucket_id) REFERENCES bucket (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO feed (id, bucket_id, name, slug, url, html_url) SELECT id, bucket_id, name, slug, url, html_url FROM __temp__feed');
$this->addSql('DROP TABLE __temp__feed');
$this->addSql('CREATE UNIQUE INDEX feed_routing ON feed (slug)');
$this->addSql('CREATE INDEX IDX_234044AB84CE584D ON feed (bucket_id)');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.');
$this->addSql('DROP TABLE entry');
$this->addSql('DROP INDEX IDX_234044AB84CE584D');
$this->addSql('DROP INDEX feed_routing');
$this->addSql('CREATE TEMPORARY TABLE __temp__feed AS SELECT id, bucket_id, name, slug, url, html_url FROM feed');
$this->addSql('DROP TABLE feed');
$this->addSql('CREATE TABLE feed (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, bucket_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, html_url VARCHAR(255) NOT NULL)');
$this->addSql('INSERT INTO feed (id, bucket_id, name, slug, url, html_url) SELECT id, bucket_id, name, slug, url, html_url FROM __temp__feed');
$this->addSql('DROP TABLE __temp__feed');
$this->addSql('CREATE INDEX IDX_234044AB84CE584D ON feed (bucket_id)');
$this->addSql('CREATE UNIQUE INDEX feed_routing ON feed (slug)');
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace App\Repository;
use App\Entity\Entry;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method Entry|null find($id, $lockMode = null, $lockVersion = null)
* @method Entry|null findOneBy(array $criteria, array $orderBy = null)
* @method Entry[] findAll()
* @method Entry[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class EntryRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Entry::class);
}
// /**
// * @return Entry[] Returns an array of Entry objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('e')
->andWhere('e.exampleField = :val')
->setParameter('val', $value)
->orderBy('e.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?Entry
{
return $this->createQueryBuilder('e')
->andWhere('e.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}

View file

@ -0,0 +1,113 @@
<?php
namespace App\Service;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use App\Entity\Entry;
use App\Entity\Feed;
use Doctrine\ORM\EntityManagerInterface;
use SimplePie;
use SimplePie_Category;
use SimplePie_Item;
use Symfony\Component\String\Slugger\SluggerInterface;
class FeedEntriesImporter
{
/**
* @var FeedParserFactory
*/
private $parserFactory;
/**
* @var EntityManager
*/
private $entityManager;
/**
* @var SluggerInterface
*/
private $slugger;
public function __construct(
FeedParserFactory $parserFactory,
EntityManagerInterface $entityManager,
SluggerInterface $slugger
) {
$this->parserFactory = $parserFactory;
$this->entityManager = $entityManager;
$this->slugger = $slugger;
}
public function importEntries(Feed $feed): void
{
$parser = $this->parserFactory->getParser();
$parser->set_feed_url($feed->getUrl());
$parser->init();
// TODO: Send event
// dump($feed->getUrl());
foreach ($parser->get_items() as $item) {
// TODO: Send event
// TODO: Check publishing date and skip if to old
$this->importEntry($item, $feed);
}
$this->entityManager->flush();
}
private function importEntry(SimplePie_Item $item, Feed $feed): void
{
$entry = new Entry(
$feed,
$item->get_id(),
$this->getDateTime($item->get_gmdate()),
$item->get_title(),
$this->slugger->slug($item->get_title()),
$item->get_link() ?? '',
$this->implodeCategories($item->get_categories()),
$item->get_content(),
);
$this->entityManager->persist($entry);
}
private function implodeCategories(?array $categories = null): string
{
if ($categories === null) {
return '';
}
return implode(',', array_map(function (SimplePie_Category $category) {
return $category->get_term();
}, $categories));
}
private function getDateTime($dateTime): \DateTimeImmutable
{
$dateTime = \DateTimeImmutable::createFromFormat(
'd M Y, h:i a',
$dateTime,
new \DateTimeZone('GMT')
);
return $dateTime->setTimezone(new \DateTimeZone(date_default_timezone_get()));
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace App\Service;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use SimplePie;
class FeedParserFactory
{
/**
* @var int
*/
private $cacheDuration;
/**
* @var string
*/
private $cacheLocation;
public function __construct(
int $cacheDuration,
string $cacheLocation
) {
$this->cacheDuration = $cacheDuration;
$this->cacheLocation = $cacheLocation;
}
public function getParser(): SimplePie
{
$parser = new SimplePie();
$parser->set_cache_duration($this->cacheDuration);
$parser->set_cache_location($this->cacheLocation);
if (is_dir($this->cacheLocation) === false) {
mkdir($this->cacheLocation);
}
return $parser;
}
}

View file

@ -129,6 +129,9 @@
"config/packages/sensio_framework_extra.yaml"
]
},
"simplepie/simplepie": {
"version": "1.5.5"
},
"symfony/asset": {
"version": "v5.0.8"
},
@ -160,6 +163,18 @@
"symfony/css-selector": {
"version": "v5.0.8"
},
"symfony/debug-bundle": {
"version": "4.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "4.1",
"ref": "f8863cbad2f2e58c4b65fa1eac892ab189971bea"
},
"files": [
"config/packages/dev/debug.yaml"
]
},
"symfony/dependency-injection": {
"version": "v5.0.8"
},
@ -225,6 +240,9 @@
"symfony/http-kernel": {
"version": "v5.0.8"
},
"symfony/intl": {
"version": "v5.0.8"
},
"symfony/maker-bundle": {
"version": "1.0",
"recipe": {
@ -258,6 +276,9 @@
"symfony/polyfill-intl-grapheme": {
"version": "v1.17.0"
},
"symfony/polyfill-intl-icu": {
"version": "v1.17.0"
},
"symfony/polyfill-intl-idn": {
"version": "v1.17.0"
},
@ -348,6 +369,12 @@
"twig/extra-bundle": {
"version": "v3.0.3"
},
"twig/intl-extra": {
"version": "v3.0.3"
},
"twig/string-extra": {
"version": "v3.0.3"
},
"twig/twig": {
"version": "v3.0.3"
},

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<title>{% block title %}{{ app_name }}{% endblock %}</title>
@ -7,7 +7,7 @@
<link href="{{ asset('css/style.css') }}" rel="stylesheet">
{% endblock %}
</head>
<body>
<body id="layout-{% block layout %}default{% endblock %}">
<header>
<h1><a href="{{ path('start') }}">{{ app_name }}</a></h1>
</header>

View file

@ -5,9 +5,11 @@
{% block body %}
<header>
<h1>Bucket: {{ bucket.name }} <small>with {{ bucket.feeds | length }} feeds</small></h1>
<nav> </nav>
</header>
{{ include('feed/entries.html.twig') }}
{# TODO: collect newest entries of all feeds in controller #}
{{ include('feed/entries.html.twig', {entries: bucket.feeds.first.entries | slice(0, 10) }) }}
<aside>
<h1>Feeds in this bucket</h1>

View file

@ -0,0 +1,32 @@
<article>
<h1><a href="{{ path('entry', {slug: entry.slug}) }}">{{ entry.name }}</a></h1>
<time>{{ entry.published | format_datetime(dateFormat = 'medium', timeFormat = 'short') }}</time>
<main>
{% if short is defined and short %}
{{ entry.content | striptags() | u.truncate(1000, '…') }}
<p><a href="{{ path('entry', {slug: entry.slug}) }}">Read more …</a></p>
{% else %}
{# TODO: Use something like parsefunc from TYPO3 to output allowed and secured content #}
{# Also in entries.html.twig? But with different configuration #}
{# <main>{{ entry.content | striptags('<p>,<a>,<strong>,<i>,<h1>,<h2>,<h3>,<h4>,<ul>,<li>') | raw }}</main> #}
{{ entry.content | striptags('<p>,<a>,<strong>,<i>,<h1>,<h2>,<h3>,<h4>,<ul>,<li>') | raw }}
{% endif %}
</main>
<nav>
{% if entry.url %}
<a href="{{ entry.url }}" target="_blank"
rel="external nofollow noopener noreferrer"
referrerpolicy="no-referrer">Entry URL</a>
{% endif %}
<a href="{{ entry.feed.url }}" target="_blank"
rel="external nofollow noopener noreferrer"
referrerpolicy="no-referrer">Feed URL</a>
<a href="{{ path('bucket', {slug: entry.feed.bucket.slug}) }}">{{ entry.feed.bucket.name }} bucket</a>
<a href="{{ path('feed', {slug: entry.feed.slug}) }}">{{ entry.feed.name }} feed</a>
</nav>
</article>

View file

@ -0,0 +1,9 @@
{% extends 'base.html.twig' %}
{% block layout %}entryDetail{% endblock %}
{% block title %}{{ entry.name }} {{entry.feed.name }} {{ entry.feed.bucket.name }}| {{ parent() }}{% endblock %}
{% block body %}
{{ include('entry/_single.html.twig', {entry: entry}) }}
{% endblock %}

View file

@ -1,2 +1,3 @@
<p>TODO: Show latest entries, need to be provided via include twig call</p>
<p>TODO: Create command to fetch entries</p>
{% for entry in entries %}
{{ include('entry/_single.html.twig', {entry: entry, short: true}) }}
{% endfor %}

View file

@ -21,6 +21,6 @@
</nav>
</header>
{{ include('feed/entries.html.twig') }}
{{ include('feed/entries.html.twig', {entries: feed.entries}) }}
{% endblock %}