Merge pull request #1653 from wallabag/v2-annotator-comments

V2 annotator comments
This commit is contained in:
Nicolas Lœuillet 2016-02-26 18:17:37 +01:00
commit 162954763e
27 changed files with 891 additions and 13 deletions

View file

@ -35,6 +35,7 @@ class AppKernel extends Kernel
new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(), new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),
new Craue\ConfigBundle\CraueConfigBundle(), new Craue\ConfigBundle\CraueConfigBundle(),
new Lexik\Bundle\MaintenanceBundle\LexikMaintenanceBundle(), new Lexik\Bundle\MaintenanceBundle\LexikMaintenanceBundle(),
new Wallabag\AnnotationBundle\WallabagAnnotationBundle(),
]; ];
if (in_array($this->getEnvironment(), ['dev', 'test'], true)) { if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {

View file

@ -1,3 +1,7 @@
wallabag_annotation:
type : rest
resource: "@WallabagAnnotationBundle/Resources/config/routing_annotations.yml"
wallabag_import: wallabag_import:
resource: "@WallabagImportBundle/Controller/" resource: "@WallabagImportBundle/Controller/"
type: annotation type: annotation

View file

@ -1,3 +1,4 @@
Rest_Wallabag: Rest_Wallabag:
type : rest type : rest
resource: "@WallabagApiBundle/Resources/config/routing_rest.yml" resource: "@WallabagApiBundle/Resources/config/routing_rest.yml"

View file

@ -58,4 +58,5 @@ security:
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: /(unread|starred|archive).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: /(unread|starred|archive).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/settings, roles: ROLE_SUPER_ADMIN } - { path: ^/settings, roles: ROLE_SUPER_ADMIN }
- { path: ^/annotations, roles: ROLE_USER }
- { path: ^/, roles: ROLE_USER } - { path: ^/, roles: ROLE_USER }

View file

@ -29,6 +29,7 @@ The main documentation for this application is organized into a couple sections:
user/configuration user/configuration
user/first_article user/first_article
user/errors_during_fetching user/errors_during_fetching
user/annotations
user/import user/import
user/download_articles user/download_articles
user/filters user/filters

View file

@ -0,0 +1,24 @@
Annotations
===========
In each article you read, you can write annotations. It's easier to understand with some pictures.
Select the part of the article that you want to annotate and click on the pencil:
.. image:: ../../img/user/annotations_1.png
:alt: Select your text
:align: center
Then, write your annotation:
.. image:: ../../img/user/annotations_2.png
:alt: Write your annotation
:align: center
The text is now highlighted and you can read your annotation if you move the mouse cursor over it.
.. image:: ../../img/user/annotations_3.png
:alt: Read your annotation
:align: center
You can create as many annotations as you wish.

View file

@ -30,6 +30,7 @@ La documentation principale de cette application est découpée en plusieurs sec
user/configuration user/configuration
user/first_article user/first_article
user/errors_during_fetching user/errors_during_fetching
user/annotations
user/import user/import
user/download_articles user/download_articles
user/filters user/filters

View file

@ -0,0 +1,25 @@
Annotations
===========
Sur chaque article que vous lisez, vous pouvez écrire des annotations. Puisqu'une image vaut mieux qu'un long discours,
voici ce que ça donne.
Sélectionnez la zone du texte que vous souhaitez annoter et cliquez sur le crayon :
.. image:: ../../img/user/annotations_1.png
:alt: Sélectionnez votre texte
:align: center
Ensuite, écrivez votre annotation :
.. image:: ../../img/user/annotations_2.png
:alt: Écrivez votre annotation
:align: center
Le texte est maintenant surligné et vous pouvez lire le annotation en le survolant avec votre souris.
.. image:: ../../img/user/annotations_3.png
:alt: Lisez votre annotation
:align: center
Vous pouvez créer autant de annotations que vous le souhaitez.

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -0,0 +1,146 @@
<?php
namespace Wallabag\AnnotationBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Wallabag\AnnotationBundle\Entity\Annotation;
use Wallabag\CoreBundle\Entity\Entry;
class WallabagAnnotationController extends FOSRestController
{
/**
* Retrieve annotations for an entry.
*
* @ApiDoc(
* requirements={
* {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
* }
* )
*
* @return Response
*/
public function getAnnotationsAction(Entry $entry)
{
$annotationRows = $this
->getDoctrine()
->getRepository('WallabagAnnotationBundle:Annotation')
->findAnnotationsByPageId($entry->getId(), $this->getUser()->getId());
$total = count($annotationRows);
$annotations = array('total' => $total, 'rows' => $annotationRows);
$json = $this->get('serializer')->serialize($annotations, 'json');
return $this->renderJsonResponse($json);
}
/**
* Creates a new annotation.
*
* @param Entry $entry
*
* @ApiDoc(
* requirements={
* {"name"="ranges", "dataType"="array", "requirement"="\w+", "description"="The range array for the annotation"},
* {"name"="quote", "dataType"="string", "required"=false, "description"="Optional, quote for the annotation"},
* {"name"="text", "dataType"="string", "required"=true, "description"=""},
* }
* )
*
* @return Response
*/
public function postAnnotationAction(Request $request, Entry $entry)
{
$data = json_decode($request->getContent(), true);
$em = $this->getDoctrine()->getManager();
$annotation = new Annotation($this->getUser());
$annotation->setText($data['text']);
if (array_key_exists('quote', $data)) {
$annotation->setQuote($data['quote']);
}
if (array_key_exists('ranges', $data)) {
$annotation->setRanges($data['ranges']);
}
$annotation->setEntry($entry);
$em->persist($annotation);
$em->flush();
$json = $this->get('serializer')->serialize($annotation, 'json');
return $this->renderJsonResponse($json);
}
/**
* Updates an annotation.
*
* @ApiDoc(
* requirements={
* {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
* }
* )
*
* @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
*
* @return Response
*/
public function putAnnotationAction(Annotation $annotation, Request $request)
{
$data = json_decode($request->getContent(), true);
if (!is_null($data['text'])) {
$annotation->setText($data['text']);
}
$em = $this->getDoctrine()->getManager();
$em->flush();
$json = $this->get('serializer')->serialize($annotation, 'json');
return $this->renderJsonResponse($json);
}
/**
* Removes an annotation.
*
* @ApiDoc(
* requirements={
* {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
* }
* )
*
* @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
*
* @return Response
*/
public function deleteAnnotationAction(Annotation $annotation)
{
$em = $this->getDoctrine()->getManager();
$em->remove($annotation);
$em->flush();
$json = $this->get('serializer')->serialize($annotation, 'json');
return $this->renderJsonResponse($json);
}
/**
* Send a JSON Response.
* We don't use the Symfony JsonRespone, because it takes an array as parameter instead of a JSON string.
*
* @param string $json
*
* @return Response
*/
private function renderJsonResponse($json, $code = 200)
{
return new Response($json, $code, array('application/json'));
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace Wallabag\AnnotationBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Wallabag\AnnotationBundle\Entity\Annotation;
class LoadAnnotationData extends AbstractFixture implements OrderedFixtureInterface
{
/**
* {@inheritdoc}
*/
public function load(ObjectManager $manager)
{
$annotation1 = new Annotation($this->getReference('admin-user'));
$annotation1->setEntry($this->getReference('entry1'));
$annotation1->setText('This is my annotation /o/');
$annotation1->setQuote('content');
$manager->persist($annotation1);
$this->addReference('annotation1', $annotation1);
$annotation2 = new Annotation($this->getReference('admin-user'));
$annotation2->setEntry($this->getReference('entry2'));
$annotation2->setText('This is my 2nd annotation /o/');
$annotation2->setQuote('content');
$manager->persist($annotation2);
$this->addReference('annotation2', $annotation2);
$manager->flush();
}
/**
* {@inheritdoc}
*/
public function getOrder()
{
return 35;
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace Wallabag\AnnotationBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('wallabag_annotation');
return $treeBuilder;
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace Wallabag\AnnotationBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
class WallabagAnnotationExtension extends Extension
{
/**
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
}
}

View file

@ -0,0 +1,270 @@
<?php
namespace Wallabag\AnnotationBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation\ExclusionPolicy;
use JMS\Serializer\Annotation\Exclude;
use JMS\Serializer\Annotation\VirtualProperty;
use JMS\Serializer\Annotation\SerializedName;
use Wallabag\UserBundle\Entity\User;
use Wallabag\CoreBundle\Entity\Entry;
/**
* Annotation.
*
* @ORM\Table(name="annotation")
* @ORM\Entity(repositoryClass="Wallabag\AnnotationBundle\Repository\AnnotationRepository")
* @ORM\HasLifecycleCallbacks()
* @ExclusionPolicy("none")
*/
class Annotation
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="text", type="text")
*/
private $text;
/**
* @var \DateTime
*
* @ORM\Column(name="created_at", type="datetime")
*/
private $createdAt;
/**
* @var \DateTime
*
* @ORM\Column(name="updated_at", type="datetime")
*/
private $updatedAt;
/**
* @var string
*
* @ORM\Column(name="quote", type="string")
*/
private $quote;
/**
* @var array
*
* @ORM\Column(name="ranges", type="array")
*/
private $ranges;
/**
* @Exclude
*
* @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User")
*/
private $user;
/**
* @Exclude
*
* @ORM\ManyToOne(targetEntity="Wallabag\CoreBundle\Entity\Entry", inversedBy="annotations")
* @ORM\JoinColumn(name="entry_id", referencedColumnName="id")
*/
private $entry;
/*
* @param User $user
*/
public function __construct(\Wallabag\UserBundle\Entity\User $user)
{
$this->user = $user;
}
/**
* Get id.
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* Set text.
*
* @param string $text
*
* @return Annotation
*/
public function setText($text)
{
$this->text = $text;
return $this;
}
/**
* Get text.
*
* @return string
*/
public function getText()
{
return $this->text;
}
/**
* @ORM\PrePersist
* @ORM\PreUpdate
*/
public function timestamps()
{
if (is_null($this->createdAt)) {
$this->createdAt = new \DateTime();
}
$this->updatedAt = new \DateTime();
}
/**
* Get created.
*
* @return \DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* Get updated.
*
* @return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* Get quote.
*
* @return string
*/
public function getQuote()
{
return $this->quote;
}
/**
* Set quote.
*
* @param string $quote
*
* @return Annotation
*/
public function setQuote($quote)
{
$this->quote = $quote;
return $this;
}
/**
* Get ranges.
*
* @return array
*/
public function getRanges()
{
return $this->ranges;
}
/**
* Set ranges.
*
* @param array $ranges
*
* @return Annotation
*/
public function setRanges($ranges)
{
$this->ranges = $ranges;
return $this;
}
/**
* Set user.
*
* @param string $user
*
* @return Annotation
*/
public function setUser($user)
{
$this->user = $user;
return $this;
}
/**
* Get user.
*
* @return string
*/
public function getUser()
{
return $this->user;
}
/**
* @VirtualProperty
* @SerializedName("user")
*/
public function getUserName()
{
return $this->user->getName();
}
/**
* Set entry.
*
* @param Entry $entry
*
* @return Annotation
*/
public function setEntry($entry)
{
$this->entry = $entry;
$entry->setAnnotation($this);
return $this;
}
/**
* Get entry.
*
* @return Entry
*/
public function getEntry()
{
return $this->entry;
}
/**
* @VirtualProperty
* @SerializedName("annotator_schema_version")
*/
public function getVersion()
{
return 'v1.0';
}
}

View file

@ -0,0 +1,91 @@
<?php
namespace Wallabag\AnnotationBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* AnnotationRepository.
*/
class AnnotationRepository extends EntityRepository
{
/**
* Return a query builder to used by other getBuilderFor* method.
*
* @param int $userId
*
* @return QueryBuilder
*/
private function getBuilderByUser($userId)
{
return $this->createQueryBuilder('a')
->leftJoin('a.user', 'u')
->andWhere('u.id = :userId')->setParameter('userId', $userId)
->orderBy('a.id', 'desc')
;
}
/**
* Retrieves all annotations for a user.
*
* @param int $userId
*
* @return QueryBuilder
*/
public function getBuilderForAllByUser($userId)
{
return $this
->getBuilderByUser($userId)
;
}
/**
* Get annotation for this id.
*
* @param int $annotationId
*
* @return array
*/
public function findAnnotationById($annotationId)
{
return $this->createQueryBuilder('a')
->andWhere('a.id = :annotationId')->setParameter('annotationId', $annotationId)
->getQuery()->getSingleResult()
;
}
/**
* Find annotations for entry id.
*
* @param int $entryId
* @param int $userId
*
* @return array
*/
public function findAnnotationsByPageId($entryId, $userId)
{
return $this->createQueryBuilder('a')
->where('a.entry = :entryId')->setParameter('entryId', $entryId)
->andwhere('a.user = :userId')->setParameter('userId', $userId)
->getQuery()->getResult()
;
}
/**
* Find last annotation for a given entry id. Used only for tests.
*
* @param int $entryId
*
* @return array
*/
public function findLastAnnotationByPageId($entryId, $userId)
{
return $this->createQueryBuilder('a')
->where('a.entry = :entryId')->setParameter('entryId', $entryId)
->andwhere('a.user = :userId')->setParameter('userId', $userId)
->orderBy('a.id', 'DESC')
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
}
}

View file

@ -0,0 +1,4 @@
annotations:
type: rest
resource: "WallabagAnnotationBundle:WallabagAnnotation"
name_prefix: annotations_

View file

@ -0,0 +1,81 @@
<?php
namespace Wallabag\AnnotationBundle\Tests\Controller;
use Wallabag\AnnotationBundle\Tests\WallabagAnnotationTestCase;
class AnnotationControllerTest extends WallabagAnnotationTestCase
{
public function testGetAnnotations()
{
$annotation = $this->client->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('WallabagAnnotationBundle:Annotation')
->findOneBy(array('user' => 1));
if (!$annotation) {
$this->markTestSkipped('No content found in db.');
}
$this->logInAs('admin');
$crawler = $this->client->request('GET', 'annotations/'.$annotation->getEntry()->getId().'.json');
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
$content = json_decode($this->client->getResponse()->getContent(), true);
$this->assertEquals(1, $content['total']);
$this->assertEquals($annotation->getText(), $content['rows'][0]['text']);
}
public function testSetAnnotation()
{
$this->logInAs('admin');
$entry = $this->client->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('WallabagCoreBundle:Entry')
->findOneBy(array('user' => 1));
$headers = array('CONTENT_TYPE' => 'application/json');
$content = json_encode(array(
'text' => 'my annotation',
'quote' => 'my quote',
'range' => '[{"start":"","startOffset":24,"end":"","endOffset":31}]',
));
$crawler = $this->client->request('POST', 'annotations/'.$entry->getId().'.json', array(), array(), $headers, $content);
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
$annotation = $this->client->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('WallabagAnnotationBundle:Annotation')
->findLastAnnotationByPageId($entry->getId(), 1);
$this->assertEquals('my annotation', $annotation->getText());
}
public function testEditAnnotation()
{
$annotation = $this->client->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('WallabagAnnotationBundle:Annotation')
->findOneBy(array('user' => 1));
$this->logInAs('admin');
$headers = array('CONTENT_TYPE' => 'application/json');
$content = json_encode(array(
'text' => 'a modified annotation',
));
$crawler = $this->client->request('PUT', 'annotations/'.$annotation->getId().'.json', array(), array(), $headers, $content);
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
$content = json_decode($this->client->getResponse()->getContent(), true);
$this->assertEquals('a modified annotation', $content['text']);
$annotationUpdated = $this->client->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('WallabagAnnotationBundle:Annotation')
->findAnnotationById($annotation->getId());
$this->assertEquals('a modified annotation', $annotationUpdated->getText());
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Wallabag\AnnotationBundle\Tests;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\BrowserKit\Cookie;
abstract class WallabagAnnotationTestCase extends WebTestCase
{
/**
* @var Client
*/
protected $client = null;
/**
* @var \FOS\UserBundle\Model\UserInterface
*/
protected $user;
public function setUp()
{
$this->client = $this->createAuthorizedClient();
}
public function logInAs($username)
{
$crawler = $this->client->request('GET', '/login');
$form = $crawler->filter('button[type=submit]')->form();
$data = array(
'_username' => $username,
'_password' => 'mypassword',
);
$this->client->submit($form, $data);
}
/**
* @return Client
*/
protected function createAuthorizedClient()
{
$client = static::createClient();
$container = $client->getContainer();
/** @var $userManager \FOS\UserBundle\Doctrine\UserManager */
$userManager = $container->get('fos_user.user_manager');
/** @var $loginManager \FOS\UserBundle\Security\LoginManager */
$loginManager = $container->get('fos_user.security.login_manager');
$firewallName = $container->getParameter('fos_user.firewall_name');
$this->user = $userManager->findUserBy(array('username' => 'admin'));
$loginManager->loginUser($firewallName, $this->user);
// save the login token into the session and put it in a cookie
$container->get('session')->set('_security_'.$firewallName, serialize($container->get('security.token_storage')->getToken()));
$container->get('session')->save();
$session = $container->get('session');
$client->getCookieJar()->set(new Cookie($session->getName(), $session->getId()));
return $client;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Wallabag\AnnotationBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class WallabagAnnotationBundle extends Bundle
{
}

View file

@ -9,6 +9,7 @@ use JMS\Serializer\Annotation\Groups;
use JMS\Serializer\Annotation\XmlRoot; use JMS\Serializer\Annotation\XmlRoot;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
use Wallabag\UserBundle\Entity\User; use Wallabag\UserBundle\Entity\User;
use Wallabag\AnnotationBundle\Entity\Annotation;
/** /**
* Entry. * Entry.
@ -98,13 +99,12 @@ class Entry
private $updatedAt; private $updatedAt;
/** /**
* @var string * @ORM\OneToMany(targetEntity="Wallabag\AnnotationBundle\Entity\Annotation", mappedBy="entry", cascade={"persist", "remove"})
* @ORM\JoinTable
* *
* @ORM\Column(name="comments", type="text", nullable=true) * @Groups({"entries_for_user", "export_all"})
*
* @Groups({"export_all"})
*/ */
private $comments; private $annotations;
/** /**
* @var string * @var string
@ -366,19 +366,19 @@ class Entry
} }
/** /**
* @return string * @return ArrayCollection<Annotation>
*/ */
public function getComments() public function getAnnotations()
{ {
return $this->comments; return $this->annotations;
} }
/** /**
* @param string $comments * @param Annotation $annotation
*/ */
public function setComments($comments) public function setAnnotation(Annotation $annotation)
{ {
$this->comments = $comments; $this->annotations[] = $annotation;
} }
/** /**

File diff suppressed because one or more lines are too long

View file

@ -98,6 +98,7 @@ Toggle favorite: 'Marquer comme favori'
Delete: 'Supprimer' Delete: 'Supprimer'
"{0} There is no entry.|{1} There is one entry.|]1,Inf[ There are %count% entries.": "{0} Il n'y a pas d'articles.|{1} Il y a un article.|]1,Inf[ Il y a %count% articles." "{0} There is no entry.|{1} There is one entry.|]1,Inf[ There are %count% entries.": "{0} Il n'y a pas d'articles.|{1} Il y a un article.|]1,Inf[ Il y a %count% articles."
http://website: "http://siteweb" http://website: "http://siteweb"
"{0} No annotations|{1} One annotation|]1,Inf[ %nbAnnotations% annotations": "{0} Aucune annotation|{1} Une annotation|]1,Inf[ %nbAnnotations% annotations"
# Edit entry # Edit entry
Edit an entry: "Éditer un article" Edit an entry: "Éditer un article"

View file

@ -38,11 +38,13 @@
<link rel="shortcut icon" type="image/x-icon" href="{{ asset('bundles/wallabagcore/themes/_global/img/appicon/favicon.ico') }}"> <link rel="shortcut icon" type="image/x-icon" href="{{ asset('bundles/wallabagcore/themes/_global/img/appicon/favicon.ico') }}">
{% block css %}{% endblock %} {% block css %}
{% endblock %}
{% block scripts %} {% block scripts %}
<script src="{{ asset('bundles/wallabagcore/themes/_global/js/jquery-2.0.3.min.js') }}"></script> <script src="{{ asset('bundles/wallabagcore/themes/_global/js/jquery-2.0.3.min.js') }}"></script>
<script src="{{ asset('bundles/wallabagcore/themes/_global/js/jquery.cookie.js') }}"></script> <script src="{{ asset('bundles/wallabagcore/themes/_global/js/jquery.cookie.js') }}"></script>
<script src="{{ asset('bundles/wallabagcore/themes/_global/js/bookmarklet.js') }}"></script> <script src="{{ asset('bundles/wallabagcore/themes/_global/js/bookmarklet.js') }}"></script>
<script src="{{ asset('bundles/wallabagcore/themes/_global/js/annotator.min.js') }}"></script>
{% endblock %} {% endblock %}
<title>{% block title %}{% endblock %}</title> <title>{% block title %}{% endblock %}</title>

View file

@ -29,7 +29,8 @@
<li><a href="mailto:hello@wallabag.org?subject=Wrong%20display%20in%20wallabag&amp;body={{ entry.url|url_encode }}" title="{% trans %}Does this article appear wrong?{% endtrans %}" class="tool bad-display icon icon-delete"><span>{% trans %}Does this article appear wrong?{% endtrans %}</span></a></li> <li><a href="mailto:hello@wallabag.org?subject=Wrong%20display%20in%20wallabag&amp;body={{ entry.url|url_encode }}" title="{% trans %}Does this article appear wrong?{% endtrans %}" class="tool bad-display icon icon-delete"><span>{% trans %}Does this article appear wrong?{% endtrans %}</span></a></li>
</ul> </ul>
</div> </div>
{% set nbAnnotations = entry.annotations | length %}
<span class="tool link mdi-communication-comment"> {% transchoice nbAnnotations %}{0} No annotations|{1} One annotation|]1,Inf[ %nbAnnotations% annotations{% endtranschoice %}</span>
<aside class="tags"> <aside class="tags">
{% for tag in entry.tags %} {% for tag in entry.tags %}
<span class="mdi-action-label-outline">{{ tag.label }}</span> <a href="{{ path('remove_tag', { 'entry': entry.id, 'tag': tag.id }) }}"><i>✘</i></a> <span class="mdi-action-label-outline">{{ tag.label }}</span> <a href="{{ path('remove_tag', { 'entry': entry.id, 'tag': tag.id }) }}"><i>✘</i></a>
@ -107,5 +108,25 @@
retrievePercent({{ entry.id }}); retrievePercent({{ entry.id }});
}); });
}); });
var app = new annotator.App();
app.include(annotator.ui.main, {
element: document.querySelector('article')
});
app.include(annotator.storage.http, {
prefix: '',
urls: {
create: '{{ path('annotations_post_annotation', { 'entry': entry.id }) }}',
update: '{{ path('annotations_put_annotation', { 'annotation': 'idAnnotation' }) }}',
destroy: '{{ path('annotations_delete_annotation', { 'annotation': 'idAnnotation' }) }}',
search: '{{ path('annotations_get_annotations', { 'entry': entry.id }) }}'
}
});
app
.start()
.then(function () {
app.annotations.load({entry: {{ entry.id }}});
});
</script> </script>
{% endblock %} {% endblock %}

View file

@ -177,6 +177,7 @@ main {
padding: 0; padding: 0;
} }
</style> </style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -186,6 +187,8 @@ main {
</header> </header>
<aside> <aside>
<a href="{{ entry.url|e }}" target="_blank" title="{% trans %}original{% endtrans %} : {{ entry.title|e }}" class="tool link mdi-content-link"> <span>{{ entry.domainName|removeWww }}</span></a> <a href="{{ entry.url|e }}" target="_blank" title="{% trans %}original{% endtrans %} : {{ entry.title|e }}" class="tool link mdi-content-link"> <span>{{ entry.domainName|removeWww }}</span></a>
{% set nbAnnotations = entry.annotations | length %}
<span class="tool link mdi-communication-comment"> {% transchoice nbAnnotations %}{0} No annotations|{1} One annotation|]1,Inf[ %nbAnnotations% annotations{% endtranschoice %}</span>
<div id="list"> <div id="list">
{% for tag in entry.tags %} {% for tag in entry.tags %}
<div class="chip"> <div class="chip">
@ -207,6 +210,29 @@ main {
{{ entry.content | raw }} {{ entry.content | raw }}
</article> </article>
</div> </div>
<script type="text/javascript">
var app = new annotator.App();
app.include(annotator.ui.main, {
element: document.querySelector('article')
});
app.include(annotator.storage.http, {
prefix: '',
urls: {
create: '{{ path('annotations_post_annotation', { 'entry': entry.id }) }}',
update: '{{ path('annotations_put_annotation', { 'annotation': 'idAnnotation' }) }}',
destroy: '{{ path('annotations_delete_annotation', { 'annotation': 'idAnnotation' }) }}',
search: '{{ path('annotations_get_annotations', { 'entry': entry.id }) }}'
}
});
app
.start()
.then(function () {
app.annotations.load({entry: {{ entry.id }}});
});
</script>
{% endblock %} {% endblock %}
{% block footer %} {% block footer %}