store estimated reading time / filters on reading time

This commit is contained in:
Nicolas Lœuillet 2015-08-07 22:20:30 +02:00
parent fedaf00537
commit 2686457448
20 changed files with 433 additions and 279 deletions

View file

@ -25,6 +25,7 @@ class AppKernel extends Kernel
new Wallabag\CoreBundle\WallabagCoreBundle(),
new Wallabag\ApiBundle\WallabagApiBundle(),
new Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle(),
new Lexik\Bundle\FormFilterBundle\LexikFormFilterBundle(),
);
if (in_array($this->getEnvironment(), array('dev', 'test'))) {

View file

@ -638,7 +638,7 @@ class SymfonyRequirements extends RequirementCollection
}
$this->addRecommendation(
class_exists('Locale'),
extension_loaded('intl'),
'intl extension should be available',
'Install and enable the <strong>intl</strong> extension (used for validators).'
);

View file

@ -42,9 +42,9 @@ foreach ($symfonyRequirements->getRecommendations() as $req) {
}
if ($checkPassed) {
echo_block('success', 'OK', 'Your system is ready to run Symfony2 projects', true);
echo_block('success', 'OK', 'Your system is ready to run Symfony2 projects');
} else {
echo_block('error', 'ERROR', 'Your system is not ready to run Symfony2 projects', true);
echo_block('error', 'ERROR', 'Your system is not ready to run Symfony2 projects');
echo_title('Fix the following mandatory requirements', 'red');

View file

@ -47,7 +47,9 @@ twig:
version: %app.version%
paypal_url: "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9UBA65LG3FX9Y&lc=gb"
flattr_url: "https://flattr.com/thing/1265480"
form:
resources:
- LexikFormFilterBundle:Form:form_div_layout.html.twig
# Assetic Configuration
assetic:
debug: "%kernel.debug%"

View file

@ -83,7 +83,8 @@
"wallabag/php-readability": "dev-master",
"wallabag/phpMobi": "dev-master",
"wallabag/Fivefilters_Libraries": "dev-master",
"pagerfanta/pagerfanta": "~1.0.3"
"pagerfanta/pagerfanta": "~1.0.3",
"lexik/form-filter-bundle": "~4.0"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "~2.2.0",

427
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,9 @@ use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Service\Extractor;
use Wallabag\CoreBundle\Form\Type\NewEntryType;
use Wallabag\CoreBundle\Form\Type\EditEntryType;
use Wallabag\CoreBundle\Filter\EntryFilterType;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
class EntryController extends Controller
{
@ -89,22 +92,39 @@ class EntryController extends Controller
/**
* Shows unread entries for current user.
*
* @param Request $request
* @param int $page
*
* @Route("/unread/list/{page}", name="unread", defaults={"page" = "1"})
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function showUnreadAction($page)
public function showUnreadAction(Request $request, $page)
{
$entries = $this->getDoctrine()
$form = $this->get('form.factory')->create(new EntryFilterType());
$filterBuilder = $this->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
->findUnreadByUser($this->getUser()->getId());
if ($request->query->has($form->getName())) {
// manually bind values from the request
$form->submit($request->query->get($form->getName()));
// build the query from the given form object
$this->get('lexik_form_filter.query_builder_updater')->addFilterConditions($form, $filterBuilder);
}
$pagerAdapter = new DoctrineORMAdapter($filterBuilder->getQuery());
$entries = new Pagerfanta($pagerAdapter);
$entries->setMaxPerPage($this->getUser()->getConfig()->getItemsPerPage());
$entries->setCurrentPage($page);
return $this->render(
'WallabagCoreBundle:Entry:entries.html.twig',
array(
'form' => $form->createView(),
'entries' => $entries,
'currentPage' => $page
)
@ -114,22 +134,39 @@ class EntryController extends Controller
/**
* Shows read entries for current user.
*
* @param Request $request
* @param int $page
*
* @Route("/archive/list/{page}", name="archive", defaults={"page" = "1"})
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function showArchiveAction($page)
public function showArchiveAction(Request $request, $page)
{
$entries = $this->getDoctrine()
$form = $this->get('form.factory')->create(new EntryFilterType());
$filterBuilder = $this->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
->findArchiveByUser($this->getUser()->getId());
if ($request->query->has($form->getName())) {
// manually bind values from the request
$form->submit($request->query->get($form->getName()));
// build the query from the given form object
$this->get('lexik_form_filter.query_builder_updater')->addFilterConditions($form, $filterBuilder);
}
$pagerAdapter = new DoctrineORMAdapter($filterBuilder->getQuery());
$entries = new Pagerfanta($pagerAdapter);
$entries->setMaxPerPage($this->getUser()->getConfig()->getItemsPerPage());
$entries->setCurrentPage($page);
return $this->render(
'WallabagCoreBundle:Entry:entries.html.twig',
array(
'form' => $form->createView(),
'entries' => $entries,
'currentPage' => $page
)
@ -139,22 +176,39 @@ class EntryController extends Controller
/**
* Shows starred entries for current user.
*
* @param Request $request
* @param int $page
*
* @Route("/starred/list/{page}", name="starred", defaults={"page" = "1"})
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function showStarredAction($page)
public function showStarredAction(Request $request, $page)
{
$entries = $this->getDoctrine()
$form = $this->get('form.factory')->create(new EntryFilterType());
$filterBuilder = $this->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
->findStarredByUser($this->getUser()->getId());
if ($request->query->has($form->getName())) {
// manually bind values from the request
$form->submit($request->query->get($form->getName()));
// build the query from the given form object
$this->get('lexik_form_filter.query_builder_updater')->addFilterConditions($form, $filterBuilder);
}
$pagerAdapter = new DoctrineORMAdapter($filterBuilder->getQuery());
$entries = new Pagerfanta($pagerAdapter);
$entries->setMaxPerPage($this->getUser()->getConfig()->getItemsPerPage());
$entries->setCurrentPage($page);
return $this->render(
'WallabagCoreBundle:Entry:entries.html.twig',
array(
'form' => $form->createView(),
'entries' => $entries,
'currentPage' => $page
)

View file

@ -7,6 +7,8 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Wallabag\CoreBundle\Entity\User;
use Wallabag\CoreBundle\Entity\Entry;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
class RssController extends Controller
{
@ -20,12 +22,15 @@ class RssController extends Controller
*/
public function showUnreadAction(User $user)
{
$entries = $this->getDoctrine()
$qb = $this->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
->findUnreadByUser(
$user->getId()
);
$pagerAdapter = new DoctrineORMAdapter($qb->getQuery());
$entries = new Pagerfanta($pagerAdapter);
$perPage = $user->getConfig()->getRssLimit() ?: $this->container->getParameter('rss_limit');
$entries->setMaxPerPage($perPage);
@ -45,12 +50,15 @@ class RssController extends Controller
*/
public function showArchiveAction(User $user)
{
$entries = $this->getDoctrine()
$qb = $this->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
->findArchiveByUser(
$user->getId()
);
$pagerAdapter = new DoctrineORMAdapter($qb->getQuery());
$entries = new Pagerfanta($pagerAdapter);
$perPage = $user->getConfig()->getRssLimit() ?: $this->container->getParameter('rss_limit');
$entries->setMaxPerPage($perPage);
@ -70,12 +78,15 @@ class RssController extends Controller
*/
public function showStarredAction(User $user)
{
$entries = $this->getDoctrine()
$qb = $this->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
->findStarredByUser(
$user->getId()
);
$pagerAdapter = new DoctrineORMAdapter($qb->getQuery());
$entries = new Pagerfanta($pagerAdapter);
$perPage = $user->getConfig()->getRssLimit() ?: $this->container->getParameter('rss_limit');
$entries->setMaxPerPage($perPage);

View file

@ -7,6 +7,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Hateoas\Configuration\Annotation as Hateoas;
use JMS\Serializer\Annotation\XmlRoot;
use Wallabag\CoreBundle\Helper\Tools;
/**
* Entry.
@ -96,7 +97,7 @@ class Entry
/**
* @var int
*
* @ORM\Column(name="reading_type", type="integer", nullable=true)
* @ORM\Column(name="reading_time", type="integer", nullable=true)
*/
private $readingTime;
@ -264,6 +265,7 @@ class Entry
public function setContent($content)
{
$this->content = $content;
$this->readingTime = Tools::getReadingTime($content);
return $this;
}

View file

@ -0,0 +1,28 @@
<?php
namespace Wallabag\CoreBundle\Filter;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class EntryFilterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('readingTime', 'filter_number_range');
}
public function getName()
{
return 'entry_filter';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => false,
'validation_groups' => array('filtering')
));
}
}

View file

@ -118,4 +118,16 @@ final class Tools
return str_replace('+', '', $token);
}
/**
* For a given text, we calculate reading time for an article.
*
* @param $text
*
* @return float
*/
public static function getReadingTime($text)
{
return floor(str_word_count(strip_tags($text)) / 200);
}
}

View file

@ -13,20 +13,15 @@ class EntryRepository extends EntityRepository
*
* @param int $userId
*
* @return Pagerfanta
* @return QueryBuilder
*/
public function findUnreadByUser($userId)
{
$qb = $this->createQueryBuilder('e')
return $this->createQueryBuilder('e')
->leftJoin('e.user', 'u')
->where('e.isArchived = false')
->andWhere('u.id =:userId')->setParameter('userId', $userId)
->orderBy('e.id', 'desc')
->getQuery();
$pagerAdapter = new DoctrineORMAdapter($qb);
return new Pagerfanta($pagerAdapter);
->orderBy('e.id', 'desc');
}
/**
@ -34,21 +29,15 @@ class EntryRepository extends EntityRepository
*
* @param int $userId
*
* @return Pagerfanta
* @return QueryBuilder
*/
public function findArchiveByUser($userId)
{
$qb = $this->createQueryBuilder('e')
->select('e')
return $this->createQueryBuilder('e')
->leftJoin('e.user', 'u')
->where('e.isArchived = true')
->andWhere('u.id =:userId')->setParameter('userId', $userId)
->orderBy('e.id', 'desc')
->getQuery();
$pagerAdapter = new DoctrineORMAdapter($qb);
return new Pagerfanta($pagerAdapter);
->orderBy('e.id', 'desc');
}
/**
@ -56,22 +45,15 @@ class EntryRepository extends EntityRepository
*
* @param int $userId
*
* @return Pagerfanta
* @return QueryBuilder
*/
public function findStarredByUser($userId)
{
$qb = $this->createQueryBuilder('e')
->select('e')
return $this->createQueryBuilder('e')
->leftJoin('e.user', 'u')
->where('e.isStarred = true')
->andWhere('u.id =:userId')->setParameter('userId', $userId)
->orderBy('e.id', 'desc')
->getQuery();
$pagerAdapter = new DoctrineORMAdapter($qb);
return new Pagerfanta($pagerAdapter);
->orderBy('e.id', 'desc');
}
/**

View file

@ -21,11 +21,12 @@
{% if entries is empty %}
<div class="messages warning"><p>{% trans %}No articles found.{% endtrans %}</p></div>
{% else %}
<div><form>{{ form_rest(form) }}<button class="btn waves-effect waves-light" type="submit" id="submit-filter" value="filter">Filter</button></form></div>
{% for entry in entries %}
<div id="entry-{{ entry.id|e }}" class="entrie">
<div id="entry-{{ entry.id|e }}" class="entry">
<h2><a href="{{ path('view', { 'id': entry.id }) }}">{{ entry.title|raw }}</a></h2>
{% if entry.content| readingTime > 0 %}
<div class="estimatedTime"><span class="tool reading-time">{% trans %}estimated reading time :{% endtrans %} {{ entry.content| readingTime }} min</span></div>
{% if entry.readingTime > 0 %}
<div class="estimatedTime"><span class="tool reading-time">{% trans %}estimated reading time :{% endtrans %} {{ entry.readingTime }} min</span></div>
{% else %}
<div class="estimatedTime"><span class="tool reading-time">{% trans %}estimated reading time :{% endtrans %} <small class="inferieur">&lt;</small> 1 min</span></div>
{% endif %}

View file

@ -17,8 +17,8 @@
<pubDate>{{ entry.createdAt|date('D, d M Y H:i:s') }}</pubDate>
<description>
<![CDATA[
{%- if entry.content|readingTime > 0 -%}
{% trans %}estimated reading time :{% endtrans %} {{ entry.content|readingTime }} min
{%- if entry.readingTime > 0 -%}
{% trans %}estimated reading time :{% endtrans %} {{ entry.readingTime }} min
{%- else -%}
{% trans %}estimated reading time :{% endtrans %} &lt; 1 min
{%- endif %}

View file

@ -121,7 +121,7 @@ a:visited {
font-size: 1.3em;
}
#main #content .entrie {
#main #content .entry {
margin-top: 15px;
padding-bottom: 15px;
border-bottom: 1px dashed #222;
@ -129,19 +129,19 @@ a:visited {
}
/* First entry */
#main #content .results + .entrie {
#main #content .results + .entry {
clear: both;
margin-top: 0;
}
#main .entrie .tools {
#main .entry .tools {
float: right;
text-align: right;
list-style-type: none;
opacity: 0.5;
}
#main .entrie .tools .tool span {
#main .entry .tools .tool span {
display: inline-block;
width: 16px;
height: 16px;

View file

@ -322,7 +322,7 @@ footer a {
letter-spacing:-5px;
}
.listmode .entrie {
.listmode .entry {
width: 100%!important;
margin-left: 0!important;
}
@ -343,7 +343,7 @@ footer a {
top: -1px;
}
.entrie {
.entry {
background-color: #FFF;
letter-spacing:normal;
box-shadow: 0 3px 7px rgba(0,0,0,0.3);
@ -366,7 +366,7 @@ footer a {
/* transition: all 0.5s ease; */
}
.entrie:before {
.entry:before {
content: "";
width: 0;
height: 0;
@ -384,7 +384,7 @@ footer a {
transition: all 0.5s ease;
}
.entrie:after {
.entry:after {
content: "";
position: absolute;
height: 7px;
@ -399,34 +399,34 @@ footer a {
transition: all 0.5s ease;
}
.entrie:hover {
.entry:hover {
box-shadow: 0 3px 10px rgba(0,0,0,1);
}
.entrie:hover:after {
.entry:hover:after {
height: 40px;
}
.entrie:hover:before {
.entry:hover:before {
bottom: 2.4em;
}
.entrie:hover h2 a {
.entry:hover h2 a {
color: #666;
}
.entrie h2 {
.entry h2 {
text-transform: none;
margin-bottom: 0;
line-height: 1.2;
}
.entrie h2:after {
.entry h2:after {
content: none;
}
.entrie h2 a {
.entry h2 a {
display: block;
text-decoration: none;
color: #000;
@ -438,7 +438,7 @@ footer a {
transition: all 0.5s ease;
}
/*
.entrie h2 a:after {
.entry h2 a:after {
content: "";
position: absolute;
top: 0;
@ -448,21 +448,21 @@ footer a {
}
*/
.entrie p {
.entry p {
color: #666;
font-size: 0.9em;
line-height: 1.7;
}
.entrie h2 a:first-letter {
.entry h2 a:first-letter {
text-transform: uppercase;
}
.entrie:hover .tools {
.entry:hover .tools {
bottom: 0;
}
.entrie .tools {
.entry .tools {
position: absolute;
bottom: -50px;
left: 0;
@ -477,22 +477,22 @@ footer a {
transition: all 0.5s ease;
}
.entrie .tools a {
.entry .tools a {
color: #666;
text-decoration: none;
display: block;
padding: 0.4em;
}
.entrie .tools a:hover {
.entry .tools a:hover {
color: #FFF;
}
.entrie .tools li {
.entry .tools li {
display: inline-block;
}
.entrie:nth-child(3n+1) {
.entry:nth-child(3n+1) {
margin-left: 0;
}
@ -941,13 +941,13 @@ pre code {
========================================================================== */
@media screen and (max-width: 1050px) {
.entrie {
.entry {
width: 49%;
}
.entrie:nth-child(3n+1) {
.entry:nth-child(3n+1) {
margin-left: 1.5%;
}
.entrie:nth-child(2n+1) {
.entry:nth-child(2n+1) {
margin-left: 0;
}
}
@ -962,7 +962,7 @@ pre code {
}
@media screen and (max-width: 700px) {
.entrie {
.entry {
width: 100%;
margin-left: 0;
}
@ -972,7 +972,7 @@ pre code {
}
@media screen and (max-width: 500px) {
.entrie {
.entry {
width: 100%;
margin-left: 0;
}

View file

@ -18,6 +18,7 @@
{% if entries is not empty %}
<div class="results clearfix">
<div class="nb-results left">{{ entries.count }} {% trans %}entries{% endtrans %}</div>
<div class="left"><form>{{ form_rest(form) }}<button class="btn waves-effect waves-light" type="submit" id="submit-filter" value="filter">Filter</button></form></div>
<ul class="pagination right">
{% for p in range(1, entries.nbPages) %}
<li class="{{ currentPage == p ? 'active':'waves-effect'}}">
@ -38,8 +39,8 @@
<div class="card">
<div class="card-content">
<span class="card-title"><a href="{{ path('view', { 'id': entry.id }) }}">{{ entry.title|raw }}</a></span>
{% if entry.content| readingTime > 0 %}
<div class="estimatedTime grey-text"><span class="tool reading-time">{% trans %}estimated reading time: {% endtrans %} {{ entry.content| readingTime }} min</span></div>
{% if entry.readingTime > 0 %}
<div class="estimatedTime grey-text"><span class="tool reading-time">{% trans %}estimated reading time: {% endtrans %} {{ entry.readingTime }} min</span></div>
{% else %}
<div class="estimatedTime grey-text"><span class="tool reading-time">{% trans %}estimated reading time: {% endtrans %} <small class="inferieur">&lt;</small> 1 min</span></div>
{% endif %}

View file

@ -25,7 +25,7 @@ body > footer,
div.tools,
header div,
.messages,
.entrie + .results {
.entry + .results {
display: none !important;
}

View file

@ -240,4 +240,23 @@ class EntryControllerTest extends WallabagCoreTestCase
$this->assertEquals(403, $client->getResponse()->getStatusCode());
}
public function testFilterOnUnreadeView()
{
$this->logInAs('admin');
$client = $this->getClient();
$crawler = $client->request('GET', '/unread/list');
$form = $crawler->filter('button[id=submit-filter]')->form();
$data = array(
'entry_filter[readingTime][right_number]' => 11,
'entry_filter[readingTime][left_number]' => 11
);
$crawler = $client->submit($form, $data);
$this->assertCount(1, $crawler->filter('div[class=entry]'));
}
}

View file

@ -7,7 +7,6 @@ class WallabagExtension extends \Twig_Extension
public function getFilters()
{
return array(
new \Twig_SimpleFilter('readingTime', array($this, 'getReadingTime')),
new \Twig_SimpleFilter('domainName', array($this, 'getDomainName')),
);
}
@ -24,18 +23,6 @@ class WallabagExtension extends \Twig_Extension
return parse_url($url, PHP_URL_HOST);
}
/**
* For a given text, we calculate reading time for an article.
*
* @param $text
*
* @return float
*/
public static function getReadingTime($text)
{
return floor(str_word_count(strip_tags($text)) / 200);
}
public function getName()
{
return 'wallabag_extension';