Merge branch 'main' into refactor-readthroughs

This commit is contained in:
Mouse Reeve 2021-09-22 15:08:58 -07:00
commit 8648bdc879
20 changed files with 173 additions and 164 deletions

View file

@ -16,7 +16,7 @@ DEFAULT_LANGUAGE="English"
MEDIA_ROOT=images/ MEDIA_ROOT=images/
POSTGRES_PORT=5432 PGPORT=5432
POSTGRES_PASSWORD=securedbypassword123 POSTGRES_PASSWORD=securedbypassword123
POSTGRES_USER=fedireads POSTGRES_USER=fedireads
POSTGRES_DB=fedireads POSTGRES_DB=fedireads

View file

@ -16,7 +16,7 @@ DEFAULT_LANGUAGE="English"
MEDIA_ROOT=images/ MEDIA_ROOT=images/
POSTGRES_PORT=5432 PGPORT=5432
POSTGRES_PASSWORD=securedbypassword123 POSTGRES_PASSWORD=securedbypassword123
POSTGRES_USER=fedireads POSTGRES_USER=fedireads
POSTGRES_DB=fedireads POSTGRES_DB=fedireads

View file

@ -14,7 +14,6 @@ from .status import Review, ReviewRating
from .status import Boost from .status import Boost
from .attachment import Image from .attachment import Image
from .favorite import Favorite from .favorite import Favorite
from .notification import Notification
from .readthrough import ReadThrough, ProgressUpdate, ProgressMode from .readthrough import ReadThrough, ProgressUpdate, ProgressMode
from .user import User, KeyPair, AnnualGoal from .user import User, KeyPair, AnnualGoal
@ -29,6 +28,8 @@ from .site import PasswordReset, InviteRequest
from .announcement import Announcement from .announcement import Announcement
from .antispam import EmailBlocklist, IPBlocklist from .antispam import EmailBlocklist, IPBlocklist
from .notification import Notification
cls_members = inspect.getmembers(sys.modules[__name__], inspect.isclass) cls_members = inspect.getmembers(sys.modules[__name__], inspect.isclass)
activity_models = { activity_models = {
c[1].activity_serializer.__name__: c[1] c[1].activity_serializer.__name__: c[1]

View file

@ -1,7 +1,5 @@
""" like/fav/star a status """ """ like/fav/star a status """
from django.apps import apps
from django.db import models from django.db import models
from django.utils import timezone
from bookwyrm import activitypub from bookwyrm import activitypub
from .activitypub_mixin import ActivityMixin from .activitypub_mixin import ActivityMixin
@ -29,38 +27,9 @@ class Favorite(ActivityMixin, BookWyrmModel):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""update user active time""" """update user active time"""
self.user.last_active_date = timezone.now() self.user.update_active_date()
self.user.save(broadcast=False, update_fields=["last_active_date"])
super().save(*args, **kwargs) super().save(*args, **kwargs)
if self.status.user.local and self.status.user != self.user:
notification_model = apps.get_model(
"bookwyrm.Notification", require_ready=True
)
notification_model.objects.create(
user=self.status.user,
notification_type="FAVORITE",
related_user=self.user,
related_status=self.status,
)
def delete(self, *args, **kwargs):
"""delete and delete notifications"""
# check for notification
if self.status.user.local:
notification_model = apps.get_model(
"bookwyrm.Notification", require_ready=True
)
notification = notification_model.objects.filter(
user=self.status.user,
related_user=self.user,
related_status=self.status,
notification_type="FAVORITE",
).first()
if notification:
notification.delete()
super().delete(*args, **kwargs)
class Meta: class Meta:
"""can't fav things twice""" """can't fav things twice"""

View file

@ -2,7 +2,6 @@
import re import re
import dateutil.parser import dateutil.parser
from django.apps import apps
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
@ -50,19 +49,6 @@ class ImportJob(models.Model):
) )
retry = models.BooleanField(default=False) retry = models.BooleanField(default=False)
def save(self, *args, **kwargs):
"""save and notify"""
super().save(*args, **kwargs)
if self.complete:
notification_model = apps.get_model(
"bookwyrm.Notification", require_ready=True
)
notification_model.objects.create(
user=self.user,
notification_type="IMPORT",
related_import=self,
)
class ImportItem(models.Model): class ImportItem(models.Model):
"""a single line of a csv being imported""" """a single line of a csv being imported"""

View file

@ -1,6 +1,8 @@
""" alert a user to activity """ """ alert a user to activity """
from django.db import models from django.db import models
from django.dispatch import receiver
from .base_model import BookWyrmModel from .base_model import BookWyrmModel
from . import Boost, Favorite, ImportJob, Report, Status, User
NotificationType = models.TextChoices( NotificationType = models.TextChoices(
@ -53,3 +55,127 @@ class Notification(BookWyrmModel):
name="notification_type_valid", name="notification_type_valid",
) )
] ]
@receiver(models.signals.post_save, sender=Favorite)
# pylint: disable=unused-argument
def notify_on_fav(sender, instance, *args, **kwargs):
"""someone liked your content, you ARE loved"""
if not instance.status.user.local or instance.status.user == instance.user:
return
Notification.objects.create(
user=instance.status.user,
notification_type="FAVORITE",
related_user=instance.user,
related_status=instance.status,
)
@receiver(models.signals.post_delete, sender=Favorite)
# pylint: disable=unused-argument
def notify_on_unfav(sender, instance, *args, **kwargs):
"""oops, didn't like that after all"""
if not instance.status.user.local:
return
Notification.objects.filter(
user=instance.status.user,
related_user=instance.user,
related_status=instance.status,
notification_type="FAVORITE",
).delete()
@receiver(models.signals.post_save)
# pylint: disable=unused-argument
def notify_user_on_mention(sender, instance, *args, **kwargs):
"""creating and deleting statuses with @ mentions and replies"""
if not issubclass(sender, Status):
return
if instance.deleted:
Notification.objects.filter(related_status=instance).delete()
return
if (
instance.reply_parent
and instance.reply_parent.user != instance.user
and instance.reply_parent.user.local
):
Notification.objects.create(
user=instance.reply_parent.user,
notification_type="REPLY",
related_user=instance.user,
related_status=instance,
)
for mention_user in instance.mention_users.all():
# avoid double-notifying about this status
if not mention_user.local or (
instance.reply_parent and mention_user == instance.reply_parent.user
):
continue
Notification.objects.create(
user=mention_user,
notification_type="MENTION",
related_user=instance.user,
related_status=instance,
)
@receiver(models.signals.post_save, sender=Boost)
# pylint: disable=unused-argument
def notify_user_on_boost(sender, instance, *args, **kwargs):
"""boosting a status"""
if (
not instance.boosted_status.user.local
or instance.boosted_status.user == instance.user
):
return
Notification.objects.create(
user=instance.boosted_status.user,
related_status=instance.boosted_status,
related_user=instance.user,
notification_type="BOOST",
)
@receiver(models.signals.post_delete, sender=Boost)
# pylint: disable=unused-argument
def notify_user_on_unboost(sender, instance, *args, **kwargs):
"""unboosting a status"""
Notification.objects.filter(
user=instance.boosted_status.user,
related_status=instance.boosted_status,
related_user=instance.user,
notification_type="BOOST",
).delete()
@receiver(models.signals.post_save, sender=ImportJob)
# pylint: disable=unused-argument
def notify_user_on_import_complete(sender, instance, *args, **kwargs):
"""we imported your books! aren't you proud of us"""
if not instance.complete:
return
Notification.objects.create(
user=instance.user,
notification_type="IMPORT",
related_import=instance,
)
@receiver(models.signals.post_save, sender=Report)
# pylint: disable=unused-argument
def notify_admins_on_report(sender, instance, *args, **kwargs):
"""something is up, make sure the admins know"""
# moderators and superusers should be notified
admins = User.objects.filter(
models.Q(user_permissions__name__in=["moderate_user", "moderate_post"])
| models.Q(is_superuser=True)
).all()
for admin in admins:
Notification.objects.create(
user=admin,
related_report=instance,
notification_type="REPORT",
)

View file

@ -2,7 +2,6 @@
from django.core import validators from django.core import validators
from django.db import models from django.db import models
from django.db.models import F, Q from django.db.models import F, Q
from django.utils import timezone
from .base_model import BookWyrmModel from .base_model import BookWyrmModel
@ -31,8 +30,7 @@ class ReadThrough(BookWyrmModel):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""update user active time""" """update user active time"""
self.user.last_active_date = timezone.now() self.user.update_active_date()
self.user.save(broadcast=False, update_fields=["last_active_date"])
super().save(*args, **kwargs) super().save(*args, **kwargs)
def create_update(self): def create_update(self):
@ -66,6 +64,5 @@ class ProgressUpdate(BookWyrmModel):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""update user active time""" """update user active time"""
self.user.last_active_date = timezone.now() self.user.update_active_date()
self.user.save(broadcast=False, update_fields=["last_active_date"])
super().save(*args, **kwargs) super().save(*args, **kwargs)

View file

@ -1,5 +1,4 @@
""" flagged for moderation """ """ flagged for moderation """
from django.apps import apps
from django.db import models from django.db import models
from django.db.models import F, Q from django.db.models import F, Q
from .base_model import BookWyrmModel from .base_model import BookWyrmModel
@ -16,23 +15,6 @@ class Report(BookWyrmModel):
statuses = models.ManyToManyField("Status", blank=True) statuses = models.ManyToManyField("Status", blank=True)
resolved = models.BooleanField(default=False) resolved = models.BooleanField(default=False)
def save(self, *args, **kwargs):
"""notify admins when a report is created"""
super().save(*args, **kwargs)
user_model = apps.get_model("bookwyrm.User", require_ready=True)
# moderators and superusers should be notified
admins = user_model.objects.filter(
Q(user_permissions__name__in=["moderate_user", "moderate_post"])
| Q(is_superuser=True)
).all()
notification_model = apps.get_model("bookwyrm.Notification", require_ready=True)
for admin in admins:
notification_model.objects.create(
user=admin,
related_report=self,
notification_type="REPORT",
)
class Meta: class Meta:
"""don't let users report themselves""" """don't let users report themselves"""

View file

@ -67,40 +67,6 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
ordering = ("-published_date",) ordering = ("-published_date",)
def save(self, *args, **kwargs):
"""save and notify"""
super().save(*args, **kwargs)
notification_model = apps.get_model("bookwyrm.Notification", require_ready=True)
if self.deleted:
notification_model.objects.filter(related_status=self).delete()
return
if (
self.reply_parent
and self.reply_parent.user != self.user
and self.reply_parent.user.local
):
notification_model.objects.create(
user=self.reply_parent.user,
notification_type="REPLY",
related_user=self.user,
related_status=self,
)
for mention_user in self.mention_users.all():
# avoid double-notifying about this status
if not mention_user.local or (
self.reply_parent and mention_user == self.reply_parent.user
):
continue
notification_model.objects.create(
user=mention_user,
notification_type="MENTION",
related_user=self.user,
related_status=self,
)
def delete(self, *args, **kwargs): # pylint: disable=unused-argument def delete(self, *args, **kwargs): # pylint: disable=unused-argument
""" "delete" a status""" """ "delete" a status"""
if hasattr(self, "boosted_status"): if hasattr(self, "boosted_status"):
@ -108,6 +74,10 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
super().delete(*args, **kwargs) super().delete(*args, **kwargs)
return return
self.deleted = True self.deleted = True
# clear user content
self.content = None
if hasattr(self, "quotation"):
self.quotation = None # pylint: disable=attribute-defined-outside-init
self.deleted_date = timezone.now() self.deleted_date = timezone.now()
self.save() self.save()
@ -386,27 +356,6 @@ class Boost(ActivityMixin, Status):
return return
super().save(*args, **kwargs) super().save(*args, **kwargs)
if not self.boosted_status.user.local or self.boosted_status.user == self.user:
return
notification_model = apps.get_model("bookwyrm.Notification", require_ready=True)
notification_model.objects.create(
user=self.boosted_status.user,
related_status=self.boosted_status,
related_user=self.user,
notification_type="BOOST",
)
def delete(self, *args, **kwargs):
"""delete and un-notify"""
notification_model = apps.get_model("bookwyrm.Notification", require_ready=True)
notification_model.objects.filter(
user=self.boosted_status.user,
related_status=self.boosted_status,
related_user=self.user,
notification_type="BOOST",
).delete()
super().delete(*args, **kwargs)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""the user field is "actor" here instead of "attributedTo" """ """the user field is "actor" here instead of "attributedTo" """
@ -419,10 +368,6 @@ class Boost(ActivityMixin, Status):
self.image_fields = [] self.image_fields = []
self.deserialize_reverse_fields = [] self.deserialize_reverse_fields = []
# This constraint can't work as it would cross tables.
# class Meta:
# unique_together = ('user', 'boosted_status')
# pylint: disable=unused-argument # pylint: disable=unused-argument
@receiver(models.signals.post_save) @receiver(models.signals.post_save)

View file

@ -195,6 +195,11 @@ class User(OrderedCollectionPageMixin, AbstractUser):
queryset = queryset.exclude(blocks=viewer) queryset = queryset.exclude(blocks=viewer)
return queryset return queryset
def update_active_date(self):
"""this user is here! they are doing things!"""
self.last_active_date = timezone.now()
self.save(broadcast=False, update_fields=["last_active_date"])
def to_outbox(self, filter_type=None, **kwargs): def to_outbox(self, filter_type=None, **kwargs):
"""an ordered collection of statuses""" """an ordered collection of statuses"""
if filter_type: if filter_type:

View file

@ -127,7 +127,7 @@ DATABASES = {
"USER": env("POSTGRES_USER", "fedireads"), "USER": env("POSTGRES_USER", "fedireads"),
"PASSWORD": env("POSTGRES_PASSWORD", "fedireads"), "PASSWORD": env("POSTGRES_PASSWORD", "fedireads"),
"HOST": env("POSTGRES_HOST", ""), "HOST": env("POSTGRES_HOST", ""),
"PORT": env("POSTGRES_PORT", 5432), "PORT": env("PGPORT", 5432),
}, },
} }

View file

@ -1,5 +1,5 @@
{% load i18n %} {% load i18n %}
<form method="post" name="goal" action="{{ request.user.local_path }}/goal/{{ year }}"> <form method="post" name="goal" action="{% url 'user-goal' request.user.localname year %}">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="year" value="{% if goal %}{{ goal.year }}{% else %}{{ year }}{% endif %}"> <input type="hidden" name="year" value="{% if goal %}{{ goal.year }}{% else %}{{ year }}{% endif %}">
<input type="hidden" name="user" value="{{ request.user.id }}"> <input type="hidden" name="user" value="{{ request.user.id }}">

View file

@ -92,7 +92,7 @@
> >
{% if status.quote %} {% if status.quote %}
<div class="quote block"> <div class="quote block">
<blockquote dir="auto" class="content mb-2">{{ status.quote|safe }}</blockquote> <blockquote dir="auto" class="content mb-2 preserve-whitespace">{{ status.quote|safe }}</blockquote>
<p> <p>
&mdash; {% include 'snippets/book_titleby.html' with book=status.book %} &mdash; {% include 'snippets/book_titleby.html' with book=status.book %}

View file

@ -10,7 +10,7 @@
{% if not no_trim and trimmed != full %} {% if not no_trim and trimmed != full %}
<div id="hide_full_{{ uuid }}"> <div id="hide_full_{{ uuid }}">
<div class="content" id="trimmed_{{ uuid }}"> <div class="content" id="trimmed_{{ uuid }}">
<div dir="auto">{{ trimmed }}</div> <div dir="auto" class="preserve-whitespace">{{ trimmed }}</div>
<div> <div>
{% if not hide_more %} {% if not hide_more %}
@ -25,6 +25,7 @@
<div class="content"> <div class="content">
<div <div
dir="auto" dir="auto"
class="preserve-whitespace"
{% if itemprop %}itemprop="{{ itemprop }}"{% endif %} {% if itemprop %}itemprop="{{ itemprop }}"{% endif %}
> >
{{ full }} {{ full }}
@ -41,6 +42,7 @@
<div class="content"> <div class="content">
<div <div
dir="auto" dir="auto"
class="preserve-whitespace"
{% if itemprop %}itemprop="{{ itemprop }}"{% endif %} {% if itemprop %}itemprop="{{ itemprop }}"{% endif %}
> >
{{ full }} {{ full }}

View file

@ -101,7 +101,7 @@ class StatusViews(TestCase):
"""@mention a user in a post""" """@mention a user in a post"""
view = views.CreateStatus.as_view() view = views.CreateStatus.as_view()
user = models.User.objects.create_user( user = models.User.objects.create_user(
"rat@%s" % DOMAIN, f"rat@{DOMAIN}",
"rat@rat.com", "rat@rat.com",
"password", "password",
local=True, local=True,
@ -124,7 +124,7 @@ class StatusViews(TestCase):
self.assertEqual(list(status.mention_users.all()), [user]) self.assertEqual(list(status.mention_users.all()), [user])
self.assertEqual(models.Notification.objects.get().user, user) self.assertEqual(models.Notification.objects.get().user, user)
self.assertEqual( self.assertEqual(
status.content, '<p>hi <a href="%s">@rat</a></p>' % user.remote_id status.content, f'<p>hi <a href="{user.remote_id}">@rat</a></p>'
) )
def test_handle_status_reply_with_mentions(self, *_): def test_handle_status_reply_with_mentions(self, *_):
@ -224,13 +224,13 @@ class StatusViews(TestCase):
def test_find_mentions(self, *_): def test_find_mentions(self, *_):
"""detect and look up @ mentions of users""" """detect and look up @ mentions of users"""
user = models.User.objects.create_user( user = models.User.objects.create_user(
"nutria@%s" % DOMAIN, f"nutria@{DOMAIN}",
"nutria@nutria.com", "nutria@nutria.com",
"password", "password",
local=True, local=True,
localname="nutria", localname="nutria",
) )
self.assertEqual(user.username, "nutria@%s" % DOMAIN) self.assertEqual(user.username, f"nutria@{DOMAIN}")
self.assertEqual( self.assertEqual(
list(views.status.find_mentions("@nutria"))[0], ("@nutria", user) list(views.status.find_mentions("@nutria"))[0], ("@nutria", user)
@ -263,19 +263,19 @@ class StatusViews(TestCase):
self.assertEqual(list(views.status.find_mentions("@beep@beep.com")), []) self.assertEqual(list(views.status.find_mentions("@beep@beep.com")), [])
self.assertEqual( self.assertEqual(
list(views.status.find_mentions("@nutria@%s" % DOMAIN))[0], list(views.status.find_mentions(f"@nutria@{DOMAIN}"))[0],
("@nutria@%s" % DOMAIN, user), (f"@nutria@{DOMAIN}", user),
) )
def test_format_links_simple_url(self, *_): def test_format_links_simple_url(self, *_):
"""find and format urls into a tags""" """find and format urls into a tags"""
url = "http://www.fish.com/" url = "http://www.fish.com/"
self.assertEqual( self.assertEqual(
views.status.format_links(url), '<a href="%s">www.fish.com/</a>' % url views.status.format_links(url), f'<a href="{url}">www.fish.com/</a>'
) )
self.assertEqual( self.assertEqual(
views.status.format_links("(%s)" % url), views.status.format_links(f"({url})"),
'(<a href="%s">www.fish.com/</a>)' % url, f'(<a href="{url}">www.fish.com/</a>)',
) )
def test_format_links_paragraph_break(self, *_): def test_format_links_paragraph_break(self, *_):
@ -292,8 +292,8 @@ http://www.fish.com/"""
"""find and format urls into a tags""" """find and format urls into a tags"""
url = "http://www.fish.com/" url = "http://www.fish.com/"
self.assertEqual( self.assertEqual(
views.status.format_links("(%s)" % url), views.status.format_links(f"({url})"),
'(<a href="%s">www.fish.com/</a>)' % url, f'(<a href="{url}">www.fish.com/</a>)',
) )
def test_format_links_special_chars(self, *_): def test_format_links_special_chars(self, *_):
@ -301,27 +301,27 @@ http://www.fish.com/"""
url = "https://archive.org/details/dli.granth.72113/page/n25/mode/2up" url = "https://archive.org/details/dli.granth.72113/page/n25/mode/2up"
self.assertEqual( self.assertEqual(
views.status.format_links(url), views.status.format_links(url),
'<a href="%s">' f'<a href="{url}">'
"archive.org/details/dli.granth.72113/page/n25/mode/2up</a>" % url, "archive.org/details/dli.granth.72113/page/n25/mode/2up</a>",
) )
url = "https://openlibrary.org/search?q=arkady+strugatsky&mode=everything" url = "https://openlibrary.org/search?q=arkady+strugatsky&mode=everything"
self.assertEqual( self.assertEqual(
views.status.format_links(url), views.status.format_links(url),
'<a href="%s">openlibrary.org/search' f'<a href="{url}">openlibrary.org/search'
"?q=arkady+strugatsky&mode=everything</a>" % url, "?q=arkady+strugatsky&mode=everything</a>",
) )
url = "https://tech.lgbt/@bookwyrm" url = "https://tech.lgbt/@bookwyrm"
self.assertEqual( self.assertEqual(
views.status.format_links(url), '<a href="%s">tech.lgbt/@bookwyrm</a>' % url views.status.format_links(url), f'<a href="{url}">tech.lgbt/@bookwyrm</a>'
) )
url = "https://users.speakeasy.net/~lion/nb/book.pdf" url = "https://users.speakeasy.net/~lion/nb/book.pdf"
self.assertEqual( self.assertEqual(
views.status.format_links(url), views.status.format_links(url),
'<a href="%s">users.speakeasy.net/~lion/nb/book.pdf</a>' % url, f'<a href="{url}">users.speakeasy.net/~lion/nb/book.pdf</a>',
) )
url = "https://pkm.one/#/page/The%20Book%20which%20launched%20a%201000%20Note%20taking%20apps" url = "https://pkm.one/#/page/The%20Book%20launched%20a%201000%20Note%20apps"
self.assertEqual( self.assertEqual(
views.status.format_links(url), '<a href="%s">%s</a>' % (url, url[8:]) views.status.format_links(url), f'<a href="{url}">{url[8:]}</a>'
) )
def test_to_markdown(self, *_): def test_to_markdown(self, *_):

View file

@ -291,7 +291,7 @@ urlpatterns = [
re_path(r"^unshelve/?$", views.unshelve), re_path(r"^unshelve/?$", views.unshelve),
# goals # goals
re_path( re_path(
rf"{USER_PATH}/goal/(?P<year>\d{4})/?$", rf"{LOCAL_USER_PATH}/goal/(?P<year>\d+)/?$",
views.Goal.as_view(), views.Goal.as_view(),
name="user-goal", name="user-goal",
), ),

View file

@ -3,7 +3,6 @@ from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect from django.shortcuts import redirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils import timezone
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
@ -54,8 +53,7 @@ class Login(View):
if user is not None: if user is not None:
# successful login # successful login
login(request, user) login(request, user)
user.last_active_date = timezone.now() user.update_active_date()
user.save(broadcast=False, update_fields=["last_active_date"])
if request.POST.get("first_login"): if request.POST.get("first_login"):
return redirect("get-started-profile") return redirect("get-started-profile")
return redirect(request.GET.get("next", "/")) return redirect(request.GET.get("next", "/"))

View file

@ -20,8 +20,6 @@ services:
- pgdata:/var/lib/postgresql/data - pgdata:/var/lib/postgresql/data
networks: networks:
- main - main
ports:
- 5432:5432
web: web:
build: . build: .
env_file: .env env_file: .env

View file

@ -1,6 +1,6 @@
celery==4.4.2 celery==4.4.2
colorthief==0.2.1 colorthief==0.2.1
Django==3.2.4 Django==3.2.5
django-imagekit==4.0.2 django-imagekit==4.0.2
django-model-utils==4.0.0 django-model-utils==4.0.0
environs==7.2.0 environs==7.2.0

View file

@ -314,9 +314,9 @@ ansi-colors@^4.1.1:
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
ansi-regex@^5.0.0: ansi-regex@^5.0.0:
version "5.0.0" version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
ansi-styles@^3.2.1: ansi-styles@^3.2.1:
version "3.2.1" version "3.2.1"