From 8efad3e1fd18452b6c641e4634bed70ae06575b6 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Tue, 26 May 2020 23:09:30 +0200 Subject: [PATCH] Add import of feed entries Basic import of entries from feeds. Also make imported feeds accessible in frontend. --- assets/sass/_elements.scss | 7 +- assets/sass/_variables.scss | 8 +- assets/sass/components/_content.scss | 3 +- assets/sass/components/_footer.scss | 2 +- assets/sass/components/_header.scss | 2 +- assets/sass/components/_sidebar.scss | 2 +- assets/sass/components/content/_entries.scss | 49 ++ composer.json | 6 +- composer.lock | 876 ++++++++++++++----- config/bundles.php | 1 + config/packages/dev/debug.yaml | 4 + config/routes.yaml | 4 + config/services.yaml | 14 + public/css/style.css | 57 +- src/Command/ImportFeedEntriesCommand.php | 45 + src/Controller/EntryController.php | 16 + src/Entity/Entry.php | 136 +++ src/Entity/Feed.php | 14 + src/Migrations/Version20200528222741.php | 56 ++ src/Repository/EntryRepository.php | 50 ++ src/Service/FeedEntriesImporter.php | 113 +++ src/Service/FeedParserFactory.php | 59 ++ symfony.lock | 27 + templates/base.html.twig | 4 +- templates/bucket/index.html.twig | 4 +- templates/entry/_single.html.twig | 32 + templates/entry/show.html.twig | 9 + templates/feed/entries.html.twig | 5 +- templates/feed/show.html.twig | 2 +- 29 files changed, 1355 insertions(+), 252 deletions(-) create mode 100644 assets/sass/components/content/_entries.scss create mode 100644 config/packages/dev/debug.yaml create mode 100644 src/Command/ImportFeedEntriesCommand.php create mode 100644 src/Controller/EntryController.php create mode 100644 src/Entity/Entry.php create mode 100644 src/Migrations/Version20200528222741.php create mode 100644 src/Repository/EntryRepository.php create mode 100644 src/Service/FeedEntriesImporter.php create mode 100644 src/Service/FeedParserFactory.php create mode 100644 templates/entry/_single.html.twig create mode 100644 templates/entry/show.html.twig diff --git a/assets/sass/_elements.scss b/assets/sass/_elements.scss index ab54b77..2b7ed93 100644 --- a/assets/sass/_elements.scss +++ b/assets/sass/_elements.scss @@ -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 { diff --git a/assets/sass/_variables.scss b/assets/sass/_variables.scss index 367c61f..71cbe5a 100644 --- a/assets/sass/_variables.scss +++ b/assets/sass/_variables.scss @@ -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; } diff --git a/assets/sass/components/_content.scss b/assets/sass/components/_content.scss index ba016b1..bfd166d 100644 --- a/assets/sass/components/_content.scss +++ b/assets/sass/components/_content.scss @@ -1,7 +1,8 @@ body > main > section { padding: var(--spacing-elements); - header h1 { margin: 0; } } + +@import 'content/entries'; diff --git a/assets/sass/components/_footer.scss b/assets/sass/components/_footer.scss index 1ccc099..66beb09 100644 --- a/assets/sass/components/_footer.scss +++ b/assets/sass/components/_footer.scss @@ -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); diff --git a/assets/sass/components/_header.scss b/assets/sass/components/_header.scss index 758db7d..6a6e169 100644 --- a/assets/sass/components/_header.scss +++ b/assets/sass/components/_header.scss @@ -1,5 +1,5 @@ body > header { - padding: var(--spacing-elements); + padding: var(--spacing-small-elements); h1 { margin: 0; diff --git a/assets/sass/components/_sidebar.scss b/assets/sass/components/_sidebar.scss index 5963dfc..b77e25e 100644 --- a/assets/sass/components/_sidebar.scss +++ b/assets/sass/components/_sidebar.scss @@ -5,6 +5,6 @@ body > main > nav { a { display: block; - padding: var(--spacing-elements); + padding: var(--spacing-small-elements); } } diff --git a/assets/sass/components/content/_entries.scss b/assets/sass/components/content/_entries.scss new file mode 100644 index 0000000..b390d87 --- /dev/null +++ b/assets/sass/components/content/_entries.scss @@ -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); + } + } + } +} diff --git a/composer.json b/composer.json index 147f57e..a5cb18b 100644 --- a/composer.json +++ b/composer.json @@ -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" }, diff --git a/composer.lock b/composer.lock index 063b94a..9589b58 100644 --- a/composer.lock +++ b/composer.lock @@ -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", diff --git a/config/bundles.php b/config/bundles.php index cdc47d3..42ecc3c 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -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], ]; diff --git a/config/packages/dev/debug.yaml b/config/packages/dev/debug.yaml new file mode 100644 index 0000000..26d4e53 --- /dev/null +++ b/config/packages/dev/debug.yaml @@ -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)%" diff --git a/config/routes.yaml b/config/routes.yaml index b928235..47c6097 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -12,3 +12,7 @@ bucket: feed: path: /feed/{slug} controller: App\Controller\FeedController::show + +entry: + path: /entry/{slug} + controller: App\Controller\EntryController::show diff --git a/config/services.yaml b/config/services.yaml index 2954b51..285373d 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -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 diff --git a/public/css/style.css b/public/css/style.css index a7c217a..943faf1 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -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 { diff --git a/src/Command/ImportFeedEntriesCommand.php b/src/Command/ImportFeedEntriesCommand.php new file mode 100644 index 0000000..01a3abc --- /dev/null +++ b/src/Command/ImportFeedEntriesCommand.php @@ -0,0 +1,45 @@ +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; + } +} diff --git a/src/Controller/EntryController.php b/src/Controller/EntryController.php new file mode 100644 index 0000000..f579517 --- /dev/null +++ b/src/Controller/EntryController.php @@ -0,0 +1,16 @@ +render('entry/show.html.twig', [ + 'entry' => $entry, + ]); + } +} diff --git a/src/Entity/Entry.php b/src/Entity/Entry.php new file mode 100644 index 0000000..1c5c40c --- /dev/null +++ b/src/Entity/Entry.php @@ -0,0 +1,136 @@ +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; + } +} diff --git a/src/Entity/Feed.php b/src/Entity/Feed.php index 422049e..fce96fd 100644 --- a/src/Entity/Feed.php +++ b/src/Entity/Feed.php @@ -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; + } } diff --git a/src/Migrations/Version20200528222741.php b/src/Migrations/Version20200528222741.php new file mode 100644 index 0000000..0915f37 --- /dev/null +++ b/src/Migrations/Version20200528222741.php @@ -0,0 +1,56 @@ +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)'); + } +} diff --git a/src/Repository/EntryRepository.php b/src/Repository/EntryRepository.php new file mode 100644 index 0000000..f489d9a --- /dev/null +++ b/src/Repository/EntryRepository.php @@ -0,0 +1,50 @@ +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() + ; + } + */ +} diff --git a/src/Service/FeedEntriesImporter.php b/src/Service/FeedEntriesImporter.php new file mode 100644 index 0000000..dd8008a --- /dev/null +++ b/src/Service/FeedEntriesImporter.php @@ -0,0 +1,113 @@ + + * + * 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())); + } +} diff --git a/src/Service/FeedParserFactory.php b/src/Service/FeedParserFactory.php new file mode 100644 index 0000000..fcbf2df --- /dev/null +++ b/src/Service/FeedParserFactory.php @@ -0,0 +1,59 @@ + + * + * 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; + } +} diff --git a/symfony.lock b/symfony.lock index eceda3a..e7c9c1a 100644 --- a/symfony.lock +++ b/symfony.lock @@ -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" }, diff --git a/templates/base.html.twig b/templates/base.html.twig index 25c0620..1bae3c3 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -1,5 +1,5 @@ - + {% block title %}{{ app_name }}{% endblock %} @@ -7,7 +7,7 @@ {% endblock %} - +

{{ app_name }}

diff --git a/templates/bucket/index.html.twig b/templates/bucket/index.html.twig index e235392..67648bb 100644 --- a/templates/bucket/index.html.twig +++ b/templates/bucket/index.html.twig @@ -5,9 +5,11 @@ {% block body %}

Bucket: {{ bucket.name }} with {{ bucket.feeds | length }} feeds

+
- {{ 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) }) }}