Merge branch 'dev' of gitlab.com:lilcity/backend into dev

remotes/origin/hasaccess
Ivlev Denis 8 years ago
commit 1b24ae1eb2
  1. 9
      api/v1/serializers/__init__.py
  2. 10
      api/v1/serializers/course.py
  3. 43
      api/v1/serializers/mixins.py
  4. 8
      api/v1/views.py
  5. 19
      apps/content/migrations/0016_auto_20180406_1816.py
  6. 19
      apps/content/migrations/0017_auto_20180406_1847.py
  7. 2
      apps/content/models.py
  8. 10
      apps/course/templates/course/_items.html
  9. 2
      apps/course/templates/course/blocks/comment.html
  10. 48
      apps/course/templates/course/content/gallery.html
  11. 16
      apps/course/templates/course/content/image.html
  12. 24
      apps/course/templates/course/content/imagetext.html
  13. 12
      apps/course/templates/course/content/text.html
  14. 28
      apps/course/templates/course/content/video.html
  15. 10
      apps/course/templates/course/course.html
  16. 14
      apps/course/templates/course/course_edit.html
  17. 4
      apps/course/templates/course/course_only_lessons.html
  18. 8
      apps/course/templates/course/lesson.html
  19. 28
      apps/course/views.py
  20. 2
      apps/notification/templates/notification/email/_base.html
  21. 2
      apps/payment/views.py
  22. 2
      apps/user/templates/user/notification-settings.html
  23. 2
      apps/user/templates/user/payment-history.html
  24. 2
      apps/user/templates/user/profile-settings.html
  25. 2
      project/settings.py
  26. 6
      project/templates/lilcity/edit_index.html
  27. 6
      project/templates/lilcity/index.html
  28. 6
      project/urls.py
  29. 7
      web/build/img/sprite.svg
  30. 4
      web/build/index.html
  31. 4
      web/build/ui-kit.html
  32. 3
      web/package.json
  33. 252
      web/src/components/CourseRedactor.vue
  34. 2
      web/src/components/LessonRedactor.vue
  35. 5
      web/src/components/blocks/BlockAdd.vue
  36. 8
      web/src/components/blocks/BlockImages.vue
  37. 7
      web/src/components/blocks/BlockText.vue
  38. 18
      web/src/icons/hamburger.svg
  39. 1
      web/src/js/app.js
  40. 26
      web/src/js/modules/api.js
  41. 2
      web/src/js/modules/comments.js
  42. 4
      web/src/js/modules/courses.js
  43. 54
      web/src/js/modules/mixpanel.js
  44. 14
      web/src/js/modules/notification.js
  45. 477
      web/src/sass/_common.sass
  46. 33
      web/src/sass/components/notification.scss
  47. 5
      web/src/sass/generated/_sprite-svg.scss
  48. 2
      web/src/templates/partials/_header.pug
  49. 2
      web/src/templates/partials/_popups.pug

@ -3,13 +3,13 @@ import base64
import six
import uuid
from django.conf import settings
from django.core.files.base import ContentFile
from rest_framework import serializers
class Base64ImageField(serializers.ImageField):
use_url = False
def to_internal_value(self, data):
if isinstance(data, six.string_types):
if 'data:' in data and ';base64,' in data:
@ -30,3 +30,8 @@ class Base64ImageField(serializers.ImageField):
extension = imghdr.what(file_name, decoded_file)
extension = "jpg" if extension == "jpeg" else extension
return extension
def to_representation(self, value):
file = "%s%s" % (settings.MEDIA_URL, super().to_representation(value),)
return file

@ -203,6 +203,7 @@ class LessonCreateSerializer(serializers.ModelSerializer):
t.title = cdata['title']
t.lesson = lesson
t.txt = cdata['txt']
t.uuid = cdata['uuid']
t.save()
else:
t = Text.objects.create(
@ -210,6 +211,7 @@ class LessonCreateSerializer(serializers.ModelSerializer):
title=cdata['title'],
lesson=lesson,
txt=cdata['txt'],
uuid=cdata['uuid'],
)
elif ctype == 'image':
if 'id' in cdata and cdata['id']:
@ -218,6 +220,7 @@ class LessonCreateSerializer(serializers.ModelSerializer):
image.title = cdata['title']
image.lesson = lesson
image.img = ImageObject.objects.get(id=cdata['img'])
image.uuid = cdata['uuid']
image.save()
else:
image = Image.objects.create(
@ -225,6 +228,7 @@ class LessonCreateSerializer(serializers.ModelSerializer):
title=cdata['title'],
lesson=lesson,
img=ImageObject.objects.get(id=cdata['img']),
uuid=cdata['uuid'],
)
elif ctype == 'image-text':
if 'id' in cdata and cdata['id']:
@ -234,6 +238,7 @@ class LessonCreateSerializer(serializers.ModelSerializer):
it.lesson = lesson
it.img = ImageObject.objects.get(id=cdata['img'])
it.txt = cdata['txt']
it.uuid = cdata['uuid']
it.save()
else:
it = ImageText.objects.create(
@ -242,6 +247,7 @@ class LessonCreateSerializer(serializers.ModelSerializer):
lesson=lesson,
img=ImageObject.objects.get(id=cdata['img']),
txt=cdata['txt'],
uuid=cdata['uuid'],
)
elif ctype == 'video':
if 'id' in cdata and cdata['id']:
@ -250,6 +256,7 @@ class LessonCreateSerializer(serializers.ModelSerializer):
v.title = cdata['title']
v.lesson = lesson
v.url = cdata['url']
v.uuid = cdata['uuid']
v.save()
else:
v = Video.objects.create(
@ -257,6 +264,7 @@ class LessonCreateSerializer(serializers.ModelSerializer):
title=cdata['title'],
lesson=lesson,
url=cdata['url'],
uuid=cdata['uuid'],
)
elif ctype == 'images':
if 'id' in cdata and cdata['id']:
@ -264,6 +272,7 @@ class LessonCreateSerializer(serializers.ModelSerializer):
g.position = cdata['position']
g.title = cdata['title']
g.lesson = lesson
g.uuid = cdata['uuid']
g.save()
if 'images' in cdata:
for image in cdata['images']:
@ -276,6 +285,7 @@ class LessonCreateSerializer(serializers.ModelSerializer):
lesson=lesson,
position=cdata['position'],
title=cdata['title'],
uuid=cdata['uuid'],
)
if 'images' in cdata:
for image in cdata['images']:

@ -22,6 +22,7 @@ class DispatchContentMixin(object):
t.title = cdata['title']
t.course = course
t.txt = cdata['txt']
t.uuid = cdata['uuid']
t.save()
else:
t = Text.objects.create(
@ -30,6 +31,7 @@ class DispatchContentMixin(object):
title=cdata['title'],
course=course,
txt=cdata['txt'],
uuid=cdata['uuid'],
)
elif ctype == 'image':
if 'id' in cdata and cdata['id']:
@ -38,7 +40,15 @@ class DispatchContentMixin(object):
image.position = cdata['position']
image.title = cdata['title']
image.course = course
image.img = ImageObject.objects.get(id=cdata['img'])
try:
image_object = ImageObject.objects.get(id=cdata['img'])
image.img = image_object
image.save()
except ImageObject.DoesNotExist:
pass
image.uuid = cdata['uuid']
image.save()
else:
image = Image.objects.create(
@ -46,8 +56,15 @@ class DispatchContentMixin(object):
position=cdata['position'],
title=cdata['title'],
course=course,
img=ImageObject.objects.get(id=cdata['img']),
uuid=cdata['uuid'],
)
try:
image_object = ImageObject.objects.get(id=cdata['img'])
image.img = image_object
image.save()
except ImageObject.DoesNotExist:
pass
elif ctype == 'image-text':
if 'id' in cdata and cdata['id']:
it = ImageText.objects.get(id=cdata['id'])
@ -55,8 +72,16 @@ class DispatchContentMixin(object):
it.position = cdata['position']
it.title = cdata['title']
it.course = course
it.img = ImageObject.objects.get(id=cdata['img'])
try:
image_object = ImageObject.objects.get(id=cdata['img'])
it.img = image_object
it.save()
except ImageObject.DoesNotExist:
pass
it.txt = cdata['txt']
it.uuid = cdata['uuid']
it.save()
else:
it = ImageText.objects.create(
@ -64,9 +89,15 @@ class DispatchContentMixin(object):
position=cdata['position'],
title=cdata['title'],
course=course,
img=ImageObject.objects.get(id=cdata['img']),
txt=cdata['txt'],
uuid=cdata['uuid'],
)
try:
image_object = ImageObject.objects.get(id=cdata['img'])
it.img = image_object
it.save()
except ImageObject.DoesNotExist:
pass
elif ctype == 'video':
if 'id' in cdata and cdata['id']:
v = Video.objects.get(id=cdata['id'])
@ -75,6 +106,7 @@ class DispatchContentMixin(object):
v.title = cdata['title']
v.course = course
v.url = cdata['url']
v.uuid = cdata['uuid']
v.save()
else:
v = Video.objects.create(
@ -83,6 +115,7 @@ class DispatchContentMixin(object):
title=cdata['title'],
course=course,
url=cdata['url'],
uuid=cdata['uuid'],
)
elif ctype == 'images':
if 'id' in cdata and cdata['id']:
@ -91,6 +124,7 @@ class DispatchContentMixin(object):
g.course = course
g.position = cdata['position']
g.title = cdata['title']
g.uuid = cdata['uuid']
g.save()
if 'images' in cdata:
for image in cdata['images']:
@ -109,6 +143,7 @@ class DispatchContentMixin(object):
course=course,
position=cdata['position'],
title=cdata['title'],
uuid=cdata['uuid'],
)
if 'images' in cdata:
for image in cdata['images']:

@ -341,11 +341,13 @@ class CommentViewSet(ExtendedModelViewSet):
queryset = self.queryset
is_deactivated = self.request.query_params.get('is_deactivated', '0')
if is_deactivated == '0':
return queryset
queryset = queryset
elif is_deactivated == '1':
return queryset.filter(deactivated_at__isnull=True)
queryset = queryset.filter(deactivated_at__isnull=True)
elif is_deactivated == '2':
return queryset.filter(deactivated_at__isnull=False)
queryset = queryset.filter(deactivated_at__isnull=False)
return queryset
class AuthorRequestViewSet(ExtendedModelViewSet):

@ -0,0 +1,19 @@
# Generated by Django 2.0.3 on 2018-04-06 18:16
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('content', '0015_content_uuid'),
]
operations = [
migrations.AlterField(
model_name='image',
name='img',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='content_images', to='content.ImageObject', verbose_name='Объект изображения'),
),
]

@ -0,0 +1,19 @@
# Generated by Django 2.0.3 on 2018-04-06 18:47
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('content', '0016_auto_20180406_1816'),
]
operations = [
migrations.AlterField(
model_name='imagetext',
name='img',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='content_imagetexts', to='content.ImageObject', verbose_name='Объект изображения'),
),
]

@ -52,6 +52,7 @@ class Image(Content):
img = models.ForeignKey(
ImageObject, related_name='content_images',
verbose_name='Объект изображения', on_delete=models.CASCADE,
null=True
)
@ -63,6 +64,7 @@ class ImageText(Content):
img = models.ForeignKey(
ImageObject, related_name='content_imagetexts',
verbose_name='Объект изображения', on_delete=models.CASCADE,
null=True
)
txt = models.TextField('Текст', default='')

@ -33,6 +33,16 @@
<div class="courses__time">ЧЕРНОВИК</div>
</div>
<div class="courses__label courses__label_draft"></div>
{% elif course.status == 3 %}
<div class="courses__soon">
<div class="courses__time">В АРХИВЕ</div>
</div>
<div class="courses__label courses__label_draft"></div>
{% elif course.status == 4 %}
<div class="courses__soon">
<div class="courses__time">ОТКЛОНЕН</div>
</div>
<div class="courses__label courses__label_draft"></div>
{% endif %}
</a>
<div class="courses__details">

@ -1,7 +1,7 @@
{% load static %}
{% if not node.deactivated_at %}
<a class="questions__anchor" id="question__{{ node.id }}"></a>
<div class="questions__item {% if node.is_child_node %}questions__item_reply{% endif %}">
<div id="question__replyto__{{ node.id }}" class="questions__item {% if node.is_child_node %}questions__item_reply{% endif %}">
{% if node.author.photo %}
<div class="questions__ava ava">
<img class="ava__pic" src="{{ node.author.photo.url }}">

@ -1,28 +1,32 @@
{% load thumbnail %}
{% if results %}
<div class="title">Галерея итогов обучения</div>
<div class="examples gallery">
{% for image in course.gallery.gallery_images.all %}
<div class="examples__item">
<a href="{{ image.img.image.url }}">
{% thumbnail image.img.image "140x140" crop="center" as im %}
<img class="examples__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
{% endthumbnail %}
</a>
<div class="title">Галерея итогов обучения</div>
<div class="examples gallery">
{% for image in course.gallery.gallery_images.all %}
<div class="examples__item">
<a href="{{ image.img.image.url }}">
{% thumbnail image.img.image "140x140" crop="center" as im %}
<img class="examples__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
{% endthumbnail %}
</a>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% else %}
<div class="content-block title">{{ content.title }}</div>
<div class="examples gallery">
{% for image in content.gallery_images.all %}
<div class="examples__item">
<a href="{{ image.img.image.url }}">
{% thumbnail image.img.image "140x140" crop="center" as im %}
<img class="examples__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
{% endthumbnail %}
</a>
<div class="section section_gradient">
<div class="section__center center center_sm">
<div class="title">{{ content.title }}</div>
<div class="examples gallery">
{% for image in content.gallery_images.all %}
<div class="examples__item">
<a href="{{ image.img.image.url }}">
{% thumbnail image.img.image "140x140" crop="center" as im %}
<img class="examples__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
{% endthumbnail %}
</a>
</div>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}

@ -1,8 +1,10 @@
<div class="content-block title">
{{ content.title }}
</div>
<div class="gallery">
<a href="{{ content.img.image.url }}">
<img class="content-block pic" src="{{ content.img.image.url }}" alt="">
</a>
<div class="section section_border">
<div class="section__center center center_sm">
<div class="content-block title">
{{ content.title }}
</div>
<div>
<img class="content-block pic" src="{{ content.img.image.url }}" alt="">
</div>
</div>
</div>

@ -1,11 +1,13 @@
<div class="content-block title">
{{ content.title }}
</div>
<div class="content-block text">
{{ content.txt | safe }}
</div>
<div class="gallery">
<a href="{{ content.img.image.url }}">
<img class="content-block pic" src="{{ content.img.image.url }}" alt="">
</a>
</div>
<div class="section section_border">
<div class="section__center center center_sm">
<div class="content-block title">{{ content.title }}</div>
<div class="lessons__item">
<div class="lessons__row">
<div class="lessons__preview"><img class="lessons__pic" style="display: block;border-radius:50%;width:130px;height: 130px" src="{{ content.img.image.url }}"></div>
<div class="lessons__content">{{ content.txt | safe }}</div>
</div>
</div>
</div>
</div>

@ -1,6 +1,10 @@
<div class="content-block title">
{{ content.title }}
<div class="section section_border">
<div class="section__center center center_sm">
<div class="content-block title">
{{ content.title }}
</div>
<div class="content-block text" style="margin-bottom:0">
{{ content.txt | safe }}
</div>
<div class="content-block text">
{{ content.txt | safe }}
</div>
</div>

@ -1,13 +1,17 @@
<div class="content-block title">
{{ content.title }}
</div>
<div>
{% if 'youtube.com' in content.url or 'youtu.be' in content.url %}
<iframe width="640" height="360" src="https://www.youtube.com/embed/{{ content.video_index }}" frameborder="0" allow="autoplay; encrypted-media"
allowfullscreen></iframe>
{% elif 'vimeo.com' in content.url %}
<iframe src="https://player.vimeo.com/video/{{ content.video_index }}" width="640" height="360" frameborder="0" webkitallowfullscreen
mozallowfullscreen allowfullscreen>
</iframe>
{% endif %}
<div class="section section_border">
<div class="section__center center center_sm">
<div class="content-block title">
{{ content.title }}
</div>
<div>
{% if 'youtube.com' in content.url or 'youtu.be' in content.url %}
<iframe width="640" height="360" src="https://www.youtube.com/embed/{{ content.video_index }}" frameborder="0" allow="autoplay; encrypted-media"
allowfullscreen></iframe>
{% elif 'vimeo.com' in content.url %}
<iframe src="https://player.vimeo.com/video/{{ content.video_index }}" width="640" height="360" frameborder="0" webkitallowfullscreen
mozallowfullscreen allowfullscreen>
</iframe>
{% endif %}
</div>
</div>
</div>

@ -174,7 +174,7 @@
{% if course.is_deferred_start %}
<div class="video__soon">
<div class="video__title">Курс начнется:</div>
<div class="video__time">{{ course.deferred_start_at_humanize }}</div>
<div class="video__time" data-future>{{ course.deferred_start_at_humanize }}</div>
</div>
{% else %}
{% comment %} <svg class="icon icon-play">
@ -240,13 +240,11 @@
{% endif %}
</div>
{% for content in course.content.all %}
<div class="section section_border">
<div class="section__center center center_sm">
{% with template="course/content/"|add:content.ctype|add:".html" %}
{% include template %}
{% endwith %}
</div>
</div>
{% endfor %}
{% if user.is_authenticated and course.lessons.exists %}
<div class="section">
@ -304,7 +302,7 @@
</div>
</div> {% endcomment %}
{% endif %}
{% if course.gallery %}
{% if False %}
<div class="section section_gradient">
<div class="section__center center center_sm">
{% include "course/content/gallery.html" with results=True %}

@ -1,13 +1,19 @@
{% extends "templates/lilcity/edit_index.html" %}
{% load static %}
{% block title %}{% if course %}Редактирование курса {{ course.title }}{% else %}Создание курса{% endif %}{% endblock title%}
{% block title %}
{% if course %}
Редактирование {% if not live %}курса{% else %}стрима{% endif %} {{ course.title }}
{% else %}
Создание {% if not live %}курса{% else %}стрима{% endif %}
{% endif %}
{% endblock title%}
{% block content %}
<course-redactor author-picture="{% if request.user.photo %}{{ request.user.photo.url }}{% else %}{% static 'img/user.jpg' %}{% endif %}"
<course-redactor :live="{{ live }}" author-picture="{% if request.user.photo %}{{ request.user.photo.url }}{% else %}{% static 'img/user.jpg' %}{% endif %}"
author-name="{{ request.user.first_name }} {{ request.user.last_name }}"
access-token="{{ request.user.auth_token }}"
{% if course and course.id %}:course-id="{{ course.id }}"{% endif %}></course-redactor>
{% endblock content %}
{% block foot %}
<script type="text/javascript" src={% static "courseRedactor.js" %}></script>
<link rel="stylesheet" href={% static "courseRedactor.css" %}/>
<script type="text/javascript" src="{% static 'courseRedactor.js' %}"></script>
<link rel="stylesheet" href="{% static 'courseRedactor.css' %}" />
{% endblock foot %}

@ -172,10 +172,6 @@
<div class="lessons__preview">
<img class="lessons__pic" src="{{ lesson.cover.url }}">
</div>
{% else %}
<div class="lessons__preview">
<img class="lessons__pic" src="{% static 'img/pic-3.jpg' %}">
</div>
{% endif %}
<div class="lessons__content">{{ lesson.short_description | safe }}</div>
</div>

@ -3,7 +3,7 @@
{% block title %}{{ lesson.title }} - {{ block.super }}{% endblock title %}
{% block content %}
<div class="section">
<div class="section" style="margin-bottom:0;padding-bottom:0">
<div class="section__center center center_sm">
<div class="go">
<a class="go__item" href="{% if next %}{{next}}{% else %}{% url 'course' lesson.course.id %}{% endif %}">
@ -42,13 +42,11 @@
</div>
</div>
{% for content in lesson.content.all %}
<div class="section section_border">
<div class="section__center center center_sm">
{% with template="course/content/"|add:content.ctype|add:".html" %}
{% include template %}
{% endwith %}
</div>
</div>
{% endfor %}
{% comment %} <div class="section section_gradient">
<div class="section__center center center_sm">

@ -155,7 +155,7 @@ class CourseOnModerationView(TemplateView):
class CourseEditView(TemplateView):
template_name = 'course/course_edit.html'
def get(self, request, pk=None):
def get(self, request, pk=None, lesson=None):
drafts = Course.objects.filter(
author=request.user, status=Course.DRAFT
)
@ -171,11 +171,37 @@ class CourseEditView(TemplateView):
def get_context_data(self):
context = super().get_context_data()
context['live'] = 'false'
if self.object:
context['course'] = self.object
return context
@method_decorator(login_required, name='dispatch')
class CourseLiveEditView(TemplateView):
template_name = 'course/course_edit.html'
def get(self, request, pk=None):
drafts = Course.objects.filter(
author=request.user, status=Course.DRAFT
)
if pk:
self.object = get_object_or_404(Course, pk=pk)
elif drafts.exists():
self.object = drafts.last()
else:
self.object = Course.objects.create()
if request.user != self.object.author and request.user.role not in [User.ADMIN_ROLE, User.AUTHOR_ROLE]:
raise Http404
return super().get(request)
def get_context_data(self):
context = super().get_context_data()
context['live'] = 'true'
if self.object:
context['course'] = self.object
return context
# @method_decorator(login_required, name='dispatch')
class CourseView(DetailView):
model = Course

@ -45,7 +45,7 @@
<table style="width:100%;padding:0;border-collapse:collapse;border-top:1px solid #979797">
<tbody>
<tr>
<td style="width:33.33%;padding:35px 0;vertical-align:middle;font-size:12px;color:#888888">2017 © Lil City, UAB. </td>
<td style="width:33.33%;padding:35px 0;vertical-align:middle;font-size:12px;color:#888888">{% now "Y" %} © Lil City, UAB. </td>
<td style="width:33.33%;padding:35px 0;vertical-align:middle;text-align:center">
<a href="#" style="display:inline-block;margin:0 5px;vertical-align:middle;font-size:0">
<img width="16" alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAAEzo7pQAAABL1BMVEUAAAD///9VVVVAQEAzM2YrK1UkSUkuLkYnJzsiM0QgMEAeLTwmJkAkMT0hLDcdJzskLjciKzwgKDgeLTweKzceKjUcKDkgKzUdJjogKjggKTcfKDYeJjUeKTgdKDYcKTYfKDgeJzcdJjgdKTcdKDYeJzceKDcdJzUdJjccKDYcKDYeJzUeJzcdKDceJzYdJzYdJzUdJzYdJjcdKDYdJzUcJzcdJjYdKDUcJjUcJjceJzYdJjcdJzYcJzUdJzYdJzYdJjUdJjUcJzYdJzUdJjYdJzYcJjYdJzUcJjUdJzUdJjYdJjYcJzUcJzYdJjUcJzYdJzUdJzYdJjYcJjYcJzUcJzYdJzYdJjUcJzYcJzYcJzUdJjYdJjYdJjUdJzUdJzUcJzYcJjYcJjUcJjUcJjWqhzSBAAAAZHRSTlMAAQMEBQYHCw0PEBEUFRcaHB4gIiorLTA1Nzg5Q0RHUVJUV1hZXGZpamxtbm90d3t8hIyNj5GTlJmam5+qrLC4uru8wMHDyM3O0tTV19nd4Obn6Onr7O3w8vP09vf4+fr7/P3+8ZYTQAAAAZRJREFUOMt1k1dj00AQhD8TK4gOCQIEoofeQdgodAOGWJQkkGDR0c3//w08nM9eKXhfpJsZbZk9gTRG/AEJqAASCTiPjcQ/MsQVuAmIznH+ehBgA0pJysF5cQ2w/QAEfIYvyBNCgNh1FhFbDOjBwKWhbryqLmXa6GUi3fx6aOGpTL5owgbgzVWAaAawV5IueVVetJIS1ZpFDyAfh+MoAboq4qBP3QDqzKZwSZghaEoPdJ7pw5JpfVnXOalHpvX9AIoDsO8bANceB+BoCcCZ58Y9gPLcFHg1Am4bP3giaatpbnB13AJWc/rr5pwJ6Bs76i5A1LcWWbrXBehrveGISbihHuQqmBuFcirFFlrsNLrW2N9SAI4MJf100q87syVqKlhY0+vD/vXid13eKXirlel3naG/FVZwTGum9mm9bAvi+sfumeCW32ejxIqqpcDf06fFHQIOvNfv+6cOLl8YSnf/MwXAiYfvNj++uLEHO2apdL6TqUYktcvm8ZmrE4gGckUat8k4LZwG0eS3z8uqveuqzBOAf9XIdaH2KUvyAAAAAElFTkSuQmCC">

@ -46,7 +46,7 @@ class CourseBuyView(TemplateView):
host = request.scheme + '://' + request.get_host()
course = Course.objects.get(id=pk)
if request.user == course.author:
messages.error('Вы не можете приобрести свой курс.')
messages.error(request, 'Вы не можете приобрести свой курс.')
return redirect(reverse_lazy('course', args=[course.id]))
course_payment = CoursePayment.objects.create(
user=request.user,

@ -16,7 +16,7 @@
<div class="confirm__content">На электронный адрес
<strong>{{ user.email }}</strong> отправлено письмо для подтверждения.</div>
<div class="confirm__content">Если письмо где-то затерялось, вы можете повторить отправку письма для подтверждения. Отправить?</div>
<a href="{% url 'resend-email-verify' %}" class="confirm__btn btn btn_dark">ОТПРАВИТЬ</a>
<a style="margin-top:18px; margin-left: 90px" href="{% url 'resend-email-verify' %}" class="confirm__btn btn btn_dark">ОТПРАВИТЬ ПИСЬМО</a>
</div>
</div>
</div>

@ -16,7 +16,7 @@
<div class="confirm__content">На электронный адрес
<strong>{{ user.email }}</strong> отправлено письмо для подтверждения.</div>
<div class="confirm__content">Если письмо где-то затерялось, вы можете повторить отправку письма для подтверждения. Отправить?</div>
<a href="{% url 'resend-email-verify' %}" class="confirm__btn btn btn_dark">ОТПРАВИТЬ</a>
<a style="margin-top:18px; margin-left: 90px" href="{% url 'resend-email-verify' %}" class="confirm__btn btn btn_dark">ОТПРАВИТЬ ПИСЬМО</a>
</div>
</div>
</div>

@ -16,7 +16,7 @@
<div class="confirm__content">На электронный адрес
<strong>{{ user.email }}</strong> отправлено письмо для подтверждения.</div>
<div class="confirm__content">Если письмо где-то затерялось, вы можете повторить отправку письма для подтверждения. Отправить?</div>
<a href="{% url 'resend-email-verify' %}" class="confirm__btn btn btn_dark">ОТПРАВИТЬ</a>
<a style="margin-top:18px; margin-left: 90px" href="{% url 'resend-email-verify' %}" class="confirm__btn btn btn_dark">ОТПРАВИТЬ ПИСЬМО</a>
</div>
</div>
</div>

@ -254,6 +254,8 @@ RAVEN_CONFIG = {
INSTAGRAM_RESULTS_PATH = 'media/instagram/results/'
DATA_UPLOAD_MAX_MEMORY_SIZE = 20242880
try:
from .local_settings import *
except ImportError:

@ -278,7 +278,7 @@
<div class="buy__row">
<div class="buy__col">
<div class="buy__head buy__head_main">
<div class="buy__title">Выбор урока/дня</div>
<div class="buy__title">Выбор курса/дня</div>
<div class="buy__content">При записи на 5 уроков скидка 10%.</div>
</div>
</div>
@ -345,6 +345,10 @@
</div>
</div>
<script type="text/javascript" src={% static "app.js" %}></script>
<script>
var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }});
var schoolAmountForDiscount = parseFloat({{ config.SERVICE_DISCOUNT_MIN_AMOUNT }});
</script>
{% block foot %}{% endblock foot %}
</body>

@ -131,7 +131,7 @@
</a> {% endcomment %}
</div>
</div>
<div class="header__group"><a class="header__section header__section_sub js-header-section {% active_link 'courses' %}" href="{% url 'courses' %}">ВИДЕО-КУРСЫ</a>
<div class="header__group"><a class="header__section header__section_sub js-header-section {% active_link 'courses' %}" href="{% url 'courses' %}">ВИДЕОКУРСЫ</a>
<div class="header__list js-header-list">
{% category_menu_items category %}
</div>
@ -248,7 +248,7 @@
</div>
<div class="footer__col footer__col_lg">
<div class="footer__group">
<div class="footer__copyright">2017 © Lil City, UAB.</div>
<div class="footer__copyright">{% now "Y" %} © Lil City, UAB.</div>
<div class="footer__links">
<a class="footer__link" href="{% url 'terms' %}">Договор-оферта</a>
<div class="footer__divider">|</div>
@ -439,7 +439,7 @@
<div class="buy__row">
<div class="buy__col">
<div class="buy__head buy__head_main">
<div class="buy__title">Выбор урока/дня</div>
<div class="buy__title">Выбор курса/дня</div>
<!-- <div class="buy__content">При записи на 5 уроков скидка 10%.</div> -->
</div>
</div>

@ -23,7 +23,7 @@ from apps.course.views import (
CoursesView, likes, coursecomment,
CourseView, LessonView, SearchView,
lessoncomment, CourseEditView,
CourseOnModerationView,
CourseOnModerationView, CourseLiveEditView,
)
from apps.user.views import (
AuthorRequestView, UserView,
@ -46,6 +46,10 @@ urlpatterns = [
path('author-request/success/', TemplateView.as_view(template_name='user/become-author-success.html'), name='author-request-success'),
path('courses/', CoursesView.as_view(), name='courses'),
path('course/create', CourseEditView.as_view(), name='course_create'),
path('course/create/lessons', CourseEditView.as_view(), name='course_create_lessons'),
path('course/create/lessons/new', CourseEditView.as_view(), name='course_create_lessons_new'),
path('course/create/lessons/edit/<int:lesson>', CourseEditView.as_view(), name='course_create_lessons_edit'),
path('course/create/live', CourseLiveEditView.as_view(), name='course_create_live'),
path('course/on-moderation', CourseOnModerationView.as_view(), name='course-on-moderation'),
path('course/<int:pk>/', CourseView.as_view(), name='course'),
path('course/<str:slug>/', CourseView.as_view(), name='course'),

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs>
<path id="a" d="M0 0h15v-2H0v2z"/>
<path id="a" fill-rule="evenodd" d="M12.5 0C3.846 0 0 7 0 7s3.846 7 12.5 7S25 7 25 7s-3.846-7-12.5-7zm0 1C4.808 1 1.154 7 1.154 7s3.654 6 11.346 6 11.346-6 11.346-6S20.192 1 12.5 1zm0 10c2.124 0 3.846-1.79 3.846-4S14.624 3 12.5 3 8.654 4.79 8.654 7s1.722 4 3.846 4zm0-1c1.593 0 2.885-1.343 2.885-3S14.093 4 12.5 4c-1.593 0-2.885 1.343-2.885 3s1.292 3 2.885 3zm0-2c.531 0 .961-.448.961-1s-.43-1-.961-1c-.531 0-.961.448-.961 1s.43 1 .961 1z"/>
<path id="a" fill-rule="evenodd" d="M4.912 13.12C1.555 11.173 0 8.5 0 8.5s3.846-6.611 12.5-6.611c1.254 0 2.408.139 3.463.376l-.817.803a14.781 14.781 0 0 0-2.646-.235C4.808 2.833 1.154 8.5 1.154 8.5s1.456 2.259 4.466 3.924l-.708.696zm4.125 1.615c1.055.237 2.209.376 3.463.376C21.154 15.111 25 8.5 25 8.5s-1.555-2.673-4.912-4.62l-.708.696C22.39 6.24 23.846 8.5 23.846 8.5s-3.654 5.667-11.346 5.667c-.942 0-1.824-.085-2.646-.235l-.817.803zM16.25 7.65c.064.273.097.557.097.849 0 2.086-1.722 3.778-3.846 3.778-.297 0-.586-.033-.864-.096l.864-.849c.738 0 1.476-.276 2.04-.83a2.799 2.799 0 0 0 .845-2.003l.864-.849zm-2.885-2.833a3.925 3.925 0 0 0-.864-.096c-2.124 0-3.846 1.692-3.846 3.778 0 .292.034.576.097.849l.864-.849c0-.725.282-1.45.845-2.003a2.902 2.902 0 0 1 2.04-.83l.864-.85zM20.192 0L3.846 16.056l.962.944L21.154.944 20.192 0z"/>
@ -24,6 +26,11 @@
<path fill-rule="evenodd" d="M6.821 20v-9h2.733L10 7H6.821V5.052C6.821 4.022 6.848 3 8.287 3h1.458V.14c0-.043-1.253-.14-2.52-.14C4.58 0 2.924 1.657 2.924 4.7V7H0v4h2.923v9h3.898z"/>
</symbol><symbol id="icon-fb" viewBox="0 0 8 18">
<path d="M2 7.5H0v-1h2v-.505A5.493 5.493 0 0 1 7.495.5v1A4.493 4.493 0 0 0 3 5.995V6.5h3v1H3v10H2v-10z"/>
</symbol><symbol id="icon-hamburger" viewBox="0 0 15 12">
<use xlink:href="#a" transform="translate(0 12)"/>
<use xlink:href="#a" transform="translate(0 7)"/>
<use xlink:href="#a" transform="translate(0 2)"/>
</symbol><symbol id="icon-image-text" viewBox="0 0 31 15">
<path fill-rule="evenodd" d="M14 13.295V1H1v8.294l4.505-4.501L14 13.295zm-.709.705L5.505 6.207 1 10.707V14h12.291zm13.229 0h-1.66V2.14h-4.22V.66h10.12v1.48h-4.24V14zM0 0h15v15H0V0zm10.5 6.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0-1a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
</symbol><symbol id="icon-image" viewBox="0 0 22 22">

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

@ -87,7 +87,7 @@
</a>
</div>
</div>
<div class="header__group"><a class="header__section header__section_sub js-header-section" href="#">ВИДЕО-КУРСЫ</a>
<div class="header__group"><a class="header__section header__section_sub js-header-section" href="#">ВИДЕОКУРСЫ</a>
<div class="header__list js-header-list">
<a class="header__link" href="#">
<div class="header__title">ПЕРСОНАЖ</div>
@ -264,7 +264,7 @@
<div class="buy__row">
<div class="buy__col">
<div class="buy__head buy__head_main">
<div class="buy__title">Выбор урока/дня</div>
<div class="buy__title">Выбор курса/дня</div>
<div class="buy__content">При записи на 5 уроков скидка 10%.</div>
</div>
</div>

@ -87,7 +87,7 @@
</a>
</div>
</div>
<div class="header__group"><a class="header__section header__section_sub js-header-section" href="#">ВИДЕО-КУРСЫ</a>
<div class="header__group"><a class="header__section header__section_sub js-header-section" href="#">ВИДЕОКУРСЫ</a>
<div class="header__list js-header-list">
<a class="header__link" href="#">
<div class="header__title">ПЕРСОНАЖ</div>
@ -352,7 +352,7 @@
<div class="buy__row">
<div class="buy__col">
<div class="buy__head buy__head_main">
<div class="buy__title">Выбор урока/дня</div>
<div class="buy__title">Выбор курса/дня</div>
<div class="buy__content">При записи на 5 уроков скидка 10%.</div>
</div>
</div>

@ -65,9 +65,12 @@
"owl.carousel": "^2.2.0",
"slugify": "^1.2.9",
"smooth-scroll": "^12.1.5",
"sortablejs": "^1.7.0",
"uuid": "^3.2.1",
"validator": "^9.2.0",
"vue": "^2.5.13",
"vue-autosize": "^1.0.2",
"vuedraggable": "^2.16.0",
"vuejs-datepicker": "^0.9.25",
"vuelidate": "^0.6.1"
}

@ -35,8 +35,20 @@
v-model="course.title"></textarea>
</div>
</div>
<div class="info__field field field_info"
v-bind:class="{ error: ($v.course.short_description.$dirty || showErrors) && $v.course.short_description.$invalid }">
<div class="field__label">КРАТКО О КУРСЕ</div>
<div class="field__wrap">
<textarea class="field__input"
rows="1"
placeholder="Кратко о курсе"
v-autosize="course.short_description"
@input="$v.course.short_description.$touch()"
v-model="course.short_description"></textarea>
</div>
</div>
</div>
<div class="info__foot">
<div class="info__foot" v-if="!live">
<div class="info__field field field_info info__field--light"
v-bind:class="{ error: ($v.course.category.$dirty || showErrors) && $v.course.category.$invalid }">
<div class="field__label field__label_gray">КАТЕГОРИЯ</div>
@ -72,7 +84,22 @@
</div>
<div class="field__wrap field__wrap--additional">{{ courseFullUrl }}</div>
</div>
<div class="info__field field">
<div v-if="live" class="info__field field">
<div class="field__label field__label_gray">ССЫЛКА НА VIMEO</div>
<div class="field__wrap">
<input type="text" class="field__input" v-model="course.stream_url">
</div>
</div>
<div v-if="live" class="info__field field">
<div class="field__label">ДАТА</div>
<div class="field__wrap">
<lil-select :value.sync="course.time" :options="dateOptions" placeholder="Выберите дату"/>
</div>
</div>
<div v-if="!live" class="info__field field">
<div class="field__label field__label_gray">ДОСТУП</div>
<div class="field__wrap">
<label class="field__switch switch switch_lg switch_circle">
@ -85,12 +112,12 @@
</label>
</div>
</div>
<label class="info__switch switch switch_lg">
<label v-if="!live" class="info__switch switch switch_lg">
<input type="checkbox" class="switch__input" v-model="course.is_featured">
<span class="switch__content">Выделить</span>
</label>
</div>
<div class="info__fieldset">
<div v-if="!live" class="info__fieldset">
<div class="info__field field">
<div class="field__label field__label_gray">ЗАПУСК</div>
<div class="field__wrap">
@ -123,23 +150,10 @@
<div class="section">
<div class="section__center center">
<div class="kit">
<div class="kit__section">
<div class="kit__field field"
v-bind:class="{ error: ($v.course.short_description.$dirty || showErrors) && $v.course.short_description.$invalid }">
<div class="field__wrap">
<textarea class="field__input"
rows="1"
placeholder="Кратко о курсе"
v-autosize="course.short_description"
@input="$v.course.short_description.$touch()"
v-model="course.short_description"></textarea>
</div>
</div>
</div>
<div id="course-redactor__nav" class="kit__nav">
<div v-if="!live" id="course-redactor__nav" class="kit__nav">
<button class="kit__btn btn btn_lg"
v-bind:class="{ 'btn_stroke': viewSection === 'course', 'btn_gray': viewSection !== 'course' }"
type="button" @click="viewSection = 'course'">Описание
type="button" @click="showCourse">Описание
курса
</button>
<button class="kit__btn btn btn_lg"
@ -151,41 +165,43 @@
</button>
</div>
<div v-if="viewSection === 'course'" class="kit__body">
<div v-for="(block, index) in course.content">
<block-text v-if="block.type === 'text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image-text v-if="block.type === 'image-text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image v-if="block.type === 'image'"
:index="index"
:title.sync="block.data.title"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-images v-if="block.type === 'images'"
<vue-draggable v-model="course.content" @start="drag=true" @end="drag=false" :options="{ handle: '.sortable__handle' }">
<div v-for="(block, index) in course.content" :key="block.data.id ? block.data.id : block.data.guid">
<block-text v-if="block.type === 'text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:images.sync="block.data.images"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-video v-if="block.type === 'video'"
:index="index"
:title.sync="block.data.title"
v-on:remove="onBlockRemoved"
:video-url.sync="block.data.video_url"/>
</div>
<block-image-text v-if="block.type === 'image-text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image v-if="block.type === 'image'"
:index="index"
:title.sync="block.data.title"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-images v-if="block.type === 'images'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:images.sync="block.data.images"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-video v-if="block.type === 'video'"
:index="index"
:title.sync="block.data.title"
v-on:remove="onBlockRemoved"
:video-url.sync="block.data.video_url"/>
</div>
</vue-draggable>
<block-add v-on:added="onBlockAdded"/>
@ -257,10 +273,15 @@
import $ from 'jquery';
import {required, minValue, numeric } from 'vuelidate/lib/validators'
import slugify from 'slugify';
import Draggable from 'vuedraggable';
import {showNotification} from "../js/modules/notification";
import createHistory from "history/createBrowserHistory";
const history = createHistory();
export default {
name: "course-redactor",
props: ["authorName", "authorPicture", "accessToken", "courseId"],
props: ["authorName", "authorPicture", "accessToken", "courseId", "live"],
data() {
return {
viewSection: 'course',
@ -295,7 +316,11 @@
lessons: [],
lessonsLoading: false,
lessonSaving: false,
currentLesson: null,
currentLesson: {
title: '',
short_description: '',
content: [],
},
is_adding_block: false,
timeOptions: [
{
@ -335,6 +360,25 @@
'value': '18:00',
}
],
dateOptions: [
{
'title': 'Акварельс (Понедельник, 2 апр)',
'value': 'Акварельс (Понедельник, 2 апр)',
},
{
'title': 'Рельсотрон (Вторник, 3 апр)',
'value': 'Рельсотрон (Вторник, 3 апр)',
},
{
'title': 'Коломёт (Среда, 4 апр)',
'value': 'Коломёт (Среда, 4 апр)',
},
{
'title': 'Зиккурат (Четверг, 5 апр)',
'value': 'Зиккурат (Четверг, 5 апр)',
},
],
showErrors: false,
savingTimeout: null,
savingDebounceTimeout: null,
@ -422,18 +466,30 @@
},
editLesson(lessonIndex) {
this.currentLesson = this.lessons[lessonIndex];
history.push("/course/create/lessons/edit/"+this.currentLesson.id);
this.viewSection = 'lessons-edit';
},
showCourse() {
if (this.viewSection !== 'course') {
history.push("/course/create");
}
this.viewSection = 'course'
},
showLessons() {
if (this.viewSection !== 'lessons') {
history.push("/course/create/lessons");
}
this.viewSection = 'lessons';
},
addLesson() {
this.currentLesson = {
title: '',
short_description: '',
course_id: this.course.id,
content: [],
};
if (this.viewSection !== 'lessons-edit') {
history.push("/course/create/lessons/new");
}
this.viewSection = 'lessons-edit';
window.scrollTo(0, 0);
},
@ -451,6 +507,7 @@
onLessonSubmit() {
this.lessonSaving = true;
const currentLessonId = this.currentLesson.id;
this.currentLesson.course_id = this.course.id;
api.saveLesson(this.currentLesson, this.accessToken)
.then((response) => {
this.lessonSaving = false;
@ -473,6 +530,7 @@
});
},
goToLessons() {
history.push("/course/create/lessons");
this.viewSection = 'lessons';
this.$nextTick(() => {
const elementTop = $('#course-redactor__nav').position().top - 130;
@ -481,7 +539,9 @@
},
loadCourseDraft() {
this.courseLoading = true;
api.getCourseDraft(this.accessToken)
let response = api.getCourseDraft(this.accessToken);
response
.then((response) => {
this.courseLoading = false;
this.course = api.convertCourseJson(response.data);
@ -493,10 +553,13 @@
this.courseLoading = false;
console.log('error course loading', err);
});
return response;
},
loadCourse() {
this.courseLoading = true;
api.loadCourse(this.courseId, this.accessToken)
let response = api.loadCourse(this.courseId, this.accessToken)
response
.then((response) => {
this.courseLoading = false;
this.course = api.convertCourseJson(response.data);
@ -508,6 +571,8 @@
this.courseLoading = false;
console.log('error course loading', err);
});
return response;
},
loadLessons(courseId) {
@ -515,6 +580,7 @@
onCoursePublish() {
this.showErrors = true;
if (this.$v.$invalid) {
showNotification("error", "Заполните все необходимые поля");
return;
}
const publishButton = $('#course-redactor__publish-button');
@ -549,7 +615,7 @@
this.savingDebounceTimeout = setTimeout(() => {
this.courseSaving = true;
clearTimeout(this.savingTimeout);
document.getElementById('course-redactor__saving-status').innerText = 'СОХРАНЕНИЕ';
document.getElementById('course-redactor__saving-status').innerText = 'СОХРАНЕНИЕ...';
const courseObject = this.course;
courseObject.url = (courseObject.url) ? slugify(courseObject.url):courseObject.url;
api.saveCourse(courseObject, this.accessToken)
@ -567,7 +633,17 @@
if (this.course.is_deferred) {
courseData.is_deferred = true;
}
this.course = courseData;
let remoteUUIDMapper = {}
if (courseData.content) {
courseData.content.forEach((contentElement) => {
remoteUUIDMapper[contentElement.uuid] = contentElement.data.id
})
}
this.course.content.forEach((contentElement, index) => {
if (!contentElement.data.id) {
this.$set(this.course.content[index].data, 'id', remoteUUIDMapper[contentElement.uuid])
}
})
if (courseData.url) {
this.slugChanged = true;
}
@ -578,11 +654,37 @@
.catch((err) => {
this.courseSyncHook = false;
this.courseSaving = false;
console.error(err);
document.getElementById('course-redactor__saving-status').innerText = 'ОШИБКА';
this.savingTimeout = setTimeout(() => {
document.getElementById('course-redactor__saving-status').innerText = '';
}, 2000);
alert('Произошло что-то страшное: '+err.toString());
});
}, 2000);
}, 500);
},
updateViewSection(location, action) {
console.log('updateViewSection[action]', action);
if (location.pathname === '/course/create/lessons') {
this.viewSection = 'lessons';
} else if (location.pathname === '/course/create') {
this.viewSection = 'course';
} else if (location.pathname === '/course/create/lessons/new') {
this.viewSection = 'lessons-edit';
} else if (location.pathname.indexOf('/course/create/lessons/edit') !== -1) {
let lessonId = parseInt(location.pathname.split('/').pop());
console.log('lessonId', lessonId, this.lessons.toString());
console.log('lessod edit', this.lessons.find((i)=>{return i.id === lessonId}));
this.currentLesson = this.lessons.find((i)=>{return i.id === lessonId});
this.viewSection = 'lessons-edit';
}
}
},
mounted() {
console.log('mounted');
// Listen for changes to the current location.
this.unlisten = history.listen(this.updateViewSection);
api.getCategories(this.accessToken)
.then((response) => {
if (response.data) {
@ -618,10 +720,13 @@
});
if (this.courseId) {
this.loadCourse()
this.loadCourse().then(()=>{this.updateViewSection(window.location, 'load')})
} else {
this.loadCourseDraft();
this.loadCourseDraft().then(()=>{this.updateViewSection(window.location, 'load draft')});
}
console.log('mounted end');
// this.updateViewSection(window.location);
},
computed: {
coverBackgroundStyle() {
@ -629,10 +734,10 @@
},
displayPrice: {
get: function () {
return this.course.is_paid ? this.course.price : 0;
return this.course.is_paid ? (this.course.price || 0) : 0;
},
set: function (value) {
this.course.price = value;
this.course.price = value || 0;
}
},
categorySelect: {
@ -677,6 +782,9 @@
return `https://lil.city/course/${suffix}`;
},
},
beforeDestroy() {
this.unlisten();
},
watch: {
'course': {
handler: function (newValue, oldValue) {
@ -707,6 +815,7 @@
'block-images': BlockImages,
'block-video': BlockVideo,
'lesson-redactor': LessonRedactor,
'vue-draggable': Draggable,
}
}
</script>
@ -776,4 +885,23 @@
width: 140px;
height: 140px;
}
</style>
.kit__section-remove {
button.sortable__handle {
margin-right: 10px;
cursor: -webkit-grab;
cursor: grab;
svg.icon-hamburger {
width: 1em;
height: 1em;
}
}
}
.sortable-ghost, .sortable-chosen {
background: white;
border-radius: 10px;
}
</style>

@ -1,6 +1,6 @@
<template>
<div class="section">
<div class="section__center center">
<div class="section__center center" v-if="lesson">
<div class="kit">
<div class="kit__go go">
<a href="#" class="go__item" @click.prevent="goBack">

@ -51,6 +51,8 @@
</template>
<script>
import uuidv4 from 'uuid/v4';
export default {
name: "block-add",
data() {
@ -60,6 +62,7 @@
},
methods: {
add(blockData) {
blockData.uuid = uuidv4();
this.isOpen = false;
this.$emit('added', blockData)
},
@ -118,4 +121,4 @@
<style scoped>
</style>
</style>

@ -1,6 +1,6 @@
<template>
<div class="kit__section kit__section--block">
<div class="kit__section-remove">
<div v-if="!readOnly" class="kit__section-remove">
<button type="button" @click="onRemove">
<svg class="icon icon-delete">
<use xlink:href="/static/img/sprite.svg#icon-delete"></use>
@ -9,14 +9,14 @@
</div>
<div class="kit__field field">
<div class="field__wrap field__wrap--title">
<input type="text"
<input :readonly="readOnly" type="text"
:value="title"
class="field__input"
placeholder="Заголовок раздела"
@change="onTitleChange">
</div>
</div>
<div class="kit__field field">
<div v-if="!readOnly" class="kit__field field">
<div class="field__wrap">
<textarea class="field__textarea field__textarea_sm"
:value="text"
@ -48,7 +48,7 @@
export default {
name: "block-images",
props: ["index", "title", "text", "images", "accessToken"],
props: ["index", "title", "text", "images", "accessToken", "readOnly"],
methods: {
onTitleChange(event) {
this.$emit('update:title', event.target.value);

@ -1,6 +1,11 @@
<template>
<div class="kit__section kit__section--block">
<div class="kit__section-remove">
<button class="sortable__handle" type="button">
<svg class="icon icon-hamburger">
<use xlink:href="/static/img/sprite.svg#icon-hamburger"></use>
</svg>
</button>
<button type="button" @click="onRemove">
<svg class="icon icon-delete">
<use xlink:href="/static/img/sprite.svg#icon-delete"></use>
@ -45,4 +50,4 @@
'vue-redactor': VueRedactor,
}
}
</script>
</script>

@ -0,0 +1,18 @@
<svg width="15" height="12" viewBox="0 0 15 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Canvas" transform="translate(-249 99)">
<g id="hamburger">
<g id="Line">
<use xlink:href="#path0_stroke" transform="translate(249 -87)" fill="#C8C8C8"/>
</g>
<g id="Line">
<use xlink:href="#path0_stroke" transform="translate(249 -92)" fill="#C8C8C8"/>
</g>
<g id="Line">
<use xlink:href="#path0_stroke" transform="translate(249 -97)" fill="#C8C8C8"/>
</g>
</g>
</g>
<defs>
<path id="path0_stroke" d="M 0 0L 15 0L 15 -2L 0 -2L 0 0Z"/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 597 B

@ -13,6 +13,7 @@ import "./modules/courses";
import "./modules/comments";
import "./modules/password-show";
import "./modules/profile";
import "./modules/notification";
import "./modules/mixpanel";
import "../sass/app.sass";

@ -83,6 +83,7 @@ export const api = {
'type': 'text',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'txt': block.data.text,
@ -93,6 +94,7 @@ export const api = {
'type': 'image',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'img': block.data.image_id,
@ -103,6 +105,7 @@ export const api = {
'type': 'image-text',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'img': block.data.image_id,
@ -114,6 +117,7 @@ export const api = {
'type': 'images',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'images': block.data.images.map((galleryImage) => {
@ -129,6 +133,7 @@ export const api = {
'type': 'video',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'url': block.data.video_url,
@ -160,6 +165,7 @@ export const api = {
'position': ++index,
'title': block.data.title,
'txt': block.data.text,
'uuid': block.uuid,
}
}
} else if (block.type === 'image') {
@ -170,6 +176,7 @@ export const api = {
'position': ++index,
'title': block.data.title,
'img': block.data.image_id,
'uuid': block.uuid,
}
}
} else if (block.type === 'image-text') {
@ -181,6 +188,7 @@ export const api = {
'title': block.data.title,
'img': block.data.image_id,
'txt': block.data.text,
'uuid': block.uuid,
}
}
} else if (block.type === 'images') {
@ -196,6 +204,7 @@ export const api = {
'img': galleryImage.img,
}
}),
'uuid': block.uuid,
}
}
} else if (block.type === 'video') {
@ -206,6 +215,7 @@ export const api = {
'position': ++index,
'title': block.data.title,
'url': block.data.video_url,
'uuid': block.uuid,
}
}
}
@ -254,6 +264,7 @@ export const api = {
coverImageId: courseJSON.cover && courseJSON.cover.id ? courseJSON.cover.id : null,
coverImage: courseJSON.cover && courseJSON.cover.image ? courseJSON.cover.image : null,
content: api.convertContentResponse(courseJSON.content),
gallery: {images: (courseJSON.gallery) ? courseJSON.gallery.gallery_images:[]},
}
},
convertContentResponse: (contentJson) => {
@ -269,6 +280,7 @@ export const api = {
if (contentItem.type === 'text') {
return {
'type': 'text',
'uuid': contentItem.uuid,
'data': {
'id': contentItem.id ? contentItem.id : null,
'title': contentItem.title,
@ -278,27 +290,30 @@ export const api = {
} else if (contentItem.type === 'image') {
return {
'type': 'image',
'uuid': contentItem.uuid,
'data': {
'id': contentItem.id ? contentItem.id : null,
'title': contentItem.title,
'image_id': contentItem.img.id,
'image_url': contentItem.img.image,
'image_id': (contentItem.img) ? contentItem.img.id:null,
'image_url': (contentItem.img) ? contentItem.img.image:null,
}
}
} else if (contentItem.type === 'image-text') {
return {
'type': 'image-text',
'uuid': contentItem.uuid,
'data': {
'id': contentItem.id ? contentItem.id : null,
'title': contentItem.title,
'image_id': contentItem.img.id,
'image_url': contentItem.img.image,
'image_id': (contentItem.img) ? contentItem.img.id:null,
'image_url': (contentItem.img) ? contentItem.img.image:null,
'text': contentItem.txt,
}
}
} else if (contentItem.type === 'images') {
return {
'type': 'images',
'uuid': contentItem.uuid,
'data': {
'id': contentItem.id ? contentItem.id : null,
'title': contentItem.title,
@ -314,6 +329,7 @@ export const api = {
} else if (contentItem.type === 'video') {
return {
'type': 'video',
'uuid': contentItem.uuid,
'data': {
'id': contentItem.id ? contentItem.id : null,
'title': contentItem.title,
@ -407,4 +423,4 @@ export const api = {
}
});
}
};
};

@ -22,7 +22,7 @@ $(document).ready(function () {
.done(function (data) {
if (data.success === true) {
if (replyToValue > 0) {
$(`#question__${replyToValue}`).after(data.comment);
$(`#question__replyto__${replyToValue}`).after(data.comment);
} else {
$('.questions__list').append(data.comment);
}

@ -17,7 +17,9 @@ $(document).ready(function () {
$('div[data-future-course]').each((_, element) => {
const courseTime = parseInt($(element).attr('data-future-course-time')) + LIL_SERVER_TIME_DIFF;
const relativeTimeString = moment(courseTime, 'X').fromNow();
$(element).find('div.courses__time').text(relativeTimeString);
$(element).find('div.video__time').text(relativeTimeString);
});
}, 1000);
@ -31,7 +33,7 @@ $(document).ready(function () {
e.preventDefault();
const currentCategory = $(this).attr('data-category-name');
$('[data-category-name]').removeClass('active');
$(`[data-category-name=${currentCategory}]`).addClass('active');
$(`[data-category-name='${currentCategory}']`).addClass('active');
history.replace($(this).attr('data-category-url'));
load_courses($(this).attr('data-category-url'), true);
});

@ -1,34 +1,36 @@
import $ from 'jquery';
$(document).ready(function (e) {
mixpanel.identify(USER_ID);
let body = $('body'),
cource = $('.course');
if (typeof mixpanel != 'undefined') {
mixpanel.identify(USER_ID);
let body = $('body'),
cource = $('.course');
if (cource.length) {
mixpanel.track(
'Open course',
{ 'course_id': COURSE_ID }
);
};
body.on('click', '[data-popup]', function (e) {
let data = $(this).data('popup');
if (data === '.js-popup-buy') {
if (cource.length) {
mixpanel.track(
'Open school buy popup'
'Open course',
{ 'course_id': COURSE_ID }
);
}
});
body.on('click', '[data-course-buy]', function (e) {
e.preventDefault();
let href = $(this).attr('href');
let t = mixpanel.track(
'Click course buy button',
{ 'course_id': COURSE_ID },
function () {
window.location = href;
};
body.on('click', '[data-popup]', function (e) {
let data = $(this).data('popup');
if (data === '.js-popup-buy') {
mixpanel.track(
'Open school buy popup'
);
}
);
});
});
body.on('click', '[data-course-buy]', function (e) {
e.preventDefault();
let href = $(this).attr('href');
let t = mixpanel.track(
'Click course buy button',
{ 'course_id': COURSE_ID },
function () {
window.location = href;
}
);
});
}
});

@ -0,0 +1,14 @@
import $ from 'jquery';
import '../../sass/components/notification.scss';
export function showNotification(style, text) {
let htmlNode = document.createElement('div');
let htmlElement = $(htmlNode).addClass('notification').addClass(`notification--${style}`).text(text).appendTo($('body'));
setTimeout(() => {
htmlElement.fadeOut(400, () => {
htmlElement.remove();
})
}, 3500);
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,33 @@
.notification {
min-width: 380px;
box-sizing: border-box;
border-radius: 4px;
border-width: 1px;
border-style: solid;
border-color: #ebeef5;
position: fixed;
left: 50%;
top: 20px;
transform: translateX(-50%);
background-color: #edf2fc;
transition: opacity 0.3s, transform .4s;
overflow: hidden;
padding: 15px 15px 15px 20px;
display: flex;
align-items: center;
z-index: 1000;
&--success {
background: #53CF86;
color: #fff;
border: none;
box-shadow: 0 4px 15px rgba(0, 196, 83, 0.31);
}
&--error {
background: #D12424;
color: #fff;
border: none;
box-shadow: 0 4px 15px rgba(255, 36, 36, 0.51);
}
}

@ -53,6 +53,11 @@
height: 1em;
fill: #1C2635;
}
.icon-hamburger {
width: 1.25em;
height: 1em;
fill: #C8C8C8;
}
.icon-image-text {
width: 2.07em;
height: 1em;

@ -65,7 +65,7 @@ else
a.header__link(href='#')
div.header__title Контакты
.header__group
a.header__section.header__section_sub.js-header-section(href='#') ВИДЕО-КУРСЫ
a.header__section.header__section_sub.js-header-section(href='#') ВИДЕОКУРСЫ
.header__list.js-header-list
a.header__link(href='#')
.header__title ПЕРСОНАЖ

@ -71,7 +71,7 @@
.buy__row
.buy__col
.buy__head.buy__head_main
.buy__title Выбор урока/дня
.buy__title Выбор курса/дня
.buy__content При записи на 5 уроков скидка 10%.
.buy__col
.buy__head

Loading…
Cancel
Save