diff --git a/fedireads/activitypub/__init__.py b/fedireads/activitypub/__init__.py
index c8eed0150..4be7ca53f 100644
--- a/fedireads/activitypub/__init__.py
+++ b/fedireads/activitypub/__init__.py
@@ -9,6 +9,7 @@ from .shelve import get_add, get_remove
from .status import get_review, get_review_article
from .status import get_rating, get_rating_note
from .status import get_comment, get_comment_article
+from .status import get_quotation, get_quotation_article
from .status import get_status, get_replies, get_replies_page
from .status import get_favorite, get_unfavorite
from .status import get_boost
diff --git a/fedireads/activitypub/status.py b/fedireads/activitypub/status.py
index 3a4f32c6a..df21c2b29 100644
--- a/fedireads/activitypub/status.py
+++ b/fedireads/activitypub/status.py
@@ -14,6 +14,29 @@ def get_rating(review):
review.rating, review.book.title)
return status
+
+def get_quotation(quotation):
+ ''' fedireads json for quotations '''
+ status = get_status(quotation)
+ status['inReplyToBook'] = quotation.book.absolute_id
+ status['fedireadsType'] = quotation.status_type
+ status['quote'] = quotation.quote
+ return status
+
+
+def get_quotation_article(quotation):
+ ''' a book quotation formatted for a non-fedireads isntance (mastodon) '''
+ status = get_status(quotation)
+ content = '"%s"
-- "%s")
%s' % (
+ quotation.quote,
+ quotation.book.absolute_id,
+ quotation.book.title,
+ quotation.content,
+ )
+ status['content'] = content
+ return status
+
+
def get_review(review):
''' fedireads json for book reviews '''
status = get_status(review)
diff --git a/fedireads/forms.py b/fedireads/forms.py
index ffbf6cd18..022925b37 100644
--- a/fedireads/forms.py
+++ b/fedireads/forms.py
@@ -53,6 +53,17 @@ class CommentForm(ModelForm):
}
+class QuotationForm(ModelForm):
+ class Meta:
+ model = models.Quotation
+ fields = ['quote', 'content']
+ help_texts = {f: None for f in fields}
+ labels = {
+ 'quote': 'Quote',
+ 'content': 'Comment',
+ }
+
+
class ReplyForm(ModelForm):
class Meta:
model = models.Status
diff --git a/fedireads/migrations/0030_quotation.py b/fedireads/migrations/0030_quotation.py
new file mode 100644
index 000000000..a88af63af
--- /dev/null
+++ b/fedireads/migrations/0030_quotation.py
@@ -0,0 +1,26 @@
+# Generated by Django 3.0.3 on 2020-04-07 00:51
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('fedireads', '0029_auto_20200403_1835'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Quotation',
+ fields=[
+ ('status_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fedireads.Status')),
+ ('quote', models.TextField()),
+ ('book', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fedireads.Edition')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=('fedireads.status',),
+ ),
+ ]
diff --git a/fedireads/models/__init__.py b/fedireads/models/__init__.py
index e843b26d3..26c7c6cc8 100644
--- a/fedireads/models/__init__.py
+++ b/fedireads/models/__init__.py
@@ -1,6 +1,7 @@
''' bring all the models into the app namespace '''
from .book import Connector, Book, Work, Edition, Author
from .shelf import Shelf, ShelfBook
-from .status import Status, Review, Comment, Favorite, Boost, Tag, Notification
+from .status import Status, Review, Comment, Quotation
+from .status import Favorite, Boost, Tag, Notification
from .user import User, UserFollows, UserFollowRequest, UserBlocks
from .user import FederatedServer
diff --git a/fedireads/models/status.py b/fedireads/models/status.py
index bec5a211e..6e4c3868b 100644
--- a/fedireads/models/status.py
+++ b/fedireads/models/status.py
@@ -56,6 +56,17 @@ class Comment(Status):
super().save(*args, **kwargs)
+class Quotation(Status):
+ ''' like a review but without a rating and transient '''
+ book = models.ForeignKey('Edition', on_delete=models.PROTECT)
+ quote = models.TextField()
+
+ def save(self, *args, **kwargs):
+ self.status_type = 'Quotation'
+ self.activity_type = 'Note'
+ super().save(*args, **kwargs)
+
+
class Review(Status):
''' a book review '''
name = models.CharField(max_length=255, null=True)
diff --git a/fedireads/outgoing.py b/fedireads/outgoing.py
index 43d3d43c4..53e1410c4 100644
--- a/fedireads/outgoing.py
+++ b/fedireads/outgoing.py
@@ -9,7 +9,8 @@ import requests
from fedireads import activitypub
from fedireads import models
from fedireads.broadcast import get_recipients, broadcast
-from fedireads.status import create_review, create_status, create_comment
+from fedireads.status import create_review, create_status
+from fedireads.status import create_quotation, create_comment
from fedireads.status import create_tag, create_notification, create_rating
from fedireads.remote_user import get_or_create_remote_user
@@ -222,6 +223,24 @@ def handle_review(user, book, name, content, rating):
broadcast(user, article_create_activity, other_recipients)
+def handle_quotation(user, book, content, quote):
+ ''' post a review '''
+ # validated and saves the review in the database so it has an id
+ quotation = create_quotation(user, book, content, quote)
+
+ quotation_activity = activitypub.get_quotation(quotation)
+ quotation_create_activity = activitypub.get_create(user, quotation_activity)
+ fr_recipients = get_recipients(user, 'public', limit='fedireads')
+ broadcast(user, quotation_create_activity, fr_recipients)
+
+ # re-format the activity for non-fedireads servers
+ article_activity = activitypub.get_quotation_article(quotation)
+ article_create_activity = activitypub.get_create(user, article_activity)
+
+ other_recipients = get_recipients(user, 'public', limit='other')
+ broadcast(user, article_create_activity, other_recipients)
+
+
def handle_comment(user, book, content):
''' post a review '''
# validated and saves the review in the database so it has an id
diff --git a/fedireads/static/format.css b/fedireads/static/format.css
index 21077896a..5ca697919 100644
--- a/fedireads/static/format.css
+++ b/fedireads/static/format.css
@@ -222,6 +222,9 @@ body {
width: 30rem;
height: 10rem;
}
+.review-form.quote-form textarea#id_content {
+ height: 4rem;
+}
@@ -578,12 +581,33 @@ input:checked ~ .compose-suggestion {
blockquote {
white-space: pre-line;
}
-blockquote .icon-quote-open {
- float: left;
+blockquote .icon-quote-open, blockquote .icon-quote-close, .quote .icon-quote-open, .quote .icon-quote-close {
font-size: 2rem;
margin-right: 0.5rem;
color: #888;
}
+blockquote .icon-quote-open, .quote .icon-quote-close {
+ float: left;
+}
+.quote {
+ margin-bottom: 2em;
+ position: relative;
+}
+.quote .icon-quote-open, .quote .icon-quote-close {
+ position: absolute;
+}
+.quote .icon-quote-open {
+ top: -0.5rem;
+}
+.quote .icon-quote-close {
+ right: 0;
+ bottom: 1.5rem;
+}
+.quote blockquote {
+ background-color: white;
+ margin: 1em;
+ padding: 1em;
+}
.interaction {
background-color: #B2DBBF;
diff --git a/fedireads/status.py b/fedireads/status.py
index 8b31a1f65..3ec94f3c0 100644
--- a/fedireads/status.py
+++ b/fedireads/status.py
@@ -56,6 +56,38 @@ def create_review(user, book, name, content, rating):
)
+def create_quotation_from_activity(author, activity):
+ ''' parse an activity json blob into a status '''
+ book = activity['inReplyToBook']
+ book = book.split('/')[-1]
+ quote = activity.get('quote')
+ content = activity.get('content')
+ published = activity.get('published')
+ remote_id = activity['id']
+
+ quotation = create_quotation(author, book, content, quote)
+ quotation.published_date = published
+ quotation.remote_id = remote_id
+ quotation.save()
+ return quotation
+
+
+def create_quotation(user, possible_book, content, quote):
+ ''' a quotation has been added '''
+ # throws a value error if the book is not found
+ book = get_or_create_book(possible_book)
+ content = sanitize(content)
+ quote = sanitize(quote)
+
+ return models.Quotation.objects.create(
+ user=user,
+ book=book,
+ content=content,
+ quote=quote,
+ )
+
+
+
def create_comment_from_activity(author, activity):
''' parse an activity json blob into a status '''
book = activity['inReplyToBook']
diff --git a/fedireads/templates/snippets/create_status.html b/fedireads/templates/snippets/create_status.html
index 2913e8a6a..1c3e7fc8e 100644
--- a/fedireads/templates/snippets/create_status.html
+++ b/fedireads/templates/snippets/create_status.html
@@ -15,8 +15,8 @@ a {{ book.title }}
{{ status.quote }}+ + +
— {% include 'snippets/book_titleby.html' with book=status.book %}
+{{ status.content | safe }}{% endif %} diff --git a/fedireads/templates/snippets/status_header.html b/fedireads/templates/snippets/status_header.html index 6efd0a574..5c58064f1 100644 --- a/fedireads/templates/snippets/status_header.html +++ b/fedireads/templates/snippets/status_header.html @@ -11,6 +11,8 @@ reviewed {{ status.book.title }} {% elif status.status_type == 'Comment' %} commented on {{ status.book.title }} +{% elif status.status_type == 'Quotation' %} + quoted {{ status.book.title }} {% elif status.status_type == 'Boost' %} boosted {% elif status.reply_parent %} diff --git a/fedireads/urls.py b/fedireads/urls.py index d9897a1a7..f8655e080 100644 --- a/fedireads/urls.py +++ b/fedireads/urls.py @@ -80,6 +80,7 @@ urlpatterns = [ re_path(r'^rate/?$', actions.rate), re_path(r'^review/?$', actions.review), + re_path(r'^quotate/?$', actions.quotate), re_path(r'^comment/?$', actions.comment), re_path(r'^tag/?$', actions.tag), re_path(r'^untag/?$', actions.untag), diff --git a/fedireads/view_actions.py b/fedireads/view_actions.py index 49c992f4e..7ccf48885 100644 --- a/fedireads/view_actions.py +++ b/fedireads/view_actions.py @@ -228,6 +228,21 @@ def review(request): return redirect('/book/%s' % book_identifier) +@login_required +def quotate(request): + ''' create a book quotation ''' + form = forms.QuotationForm(request.POST) + book_identifier = request.POST.get('book') + if not form.is_valid(): + return redirect('/book/%s' % book_identifier) + + quote = form.cleaned_data.get('quote') + content = form.cleaned_data.get('content') + + outgoing.handle_quotation(request.user, book_identifier, content, quote) + return redirect('/book/%s' % book_identifier) + + @login_required def comment(request): ''' create a book comment ''' diff --git a/fedireads/views.py b/fedireads/views.py index d04f2a55d..544faf1f0 100644 --- a/fedireads/views.py +++ b/fedireads/views.py @@ -92,6 +92,7 @@ def home_tab(request, tab): ], 'active_tab': tab, 'review_form': forms.ReviewForm(), + 'quotation_form': forms.QuotationForm(), 'comment_form': forms.CommentForm(), 'next': next_page if activity_count > (page_size * page) else None, 'prev': prev_page if page > 1 else None, diff --git a/fr-dev b/fr-dev index 319eae78e..f697770d7 100755 --- a/fr-dev +++ b/fr-dev @@ -36,6 +36,5 @@ case "$1" in ;; *) echo "Unrecognised command. Try: up, initdb, resetdb,makemigrations, migrate, shell, dbshell " - docker-compose build ;; esac