Merge branch 'master' of gitlab.com:lilcity/backend into hotfix/LIL-724

remotes/origin/hotfix/LIL-724
gzbender 7 years ago
commit 236fd212fc
  1. 7
      api/v1/views.py
  2. 7
      apps/content/admin.py
  3. 5
      apps/content/templates/content/contest.html
  4. 9
      apps/content/templates/content/contest_edit.html
  5. 0
      apps/course/management/__init__.py
  6. 0
      apps/course/management/commands/__init__.py
  7. 20
      apps/course/management/commands/update_courses_slug.py
  8. 25
      apps/course/models.py
  9. 3
      apps/course/templates/course/_items.html
  10. 3
      apps/course/templates/course/course.html
  11. 17
      apps/course/templates/course/course_edit.html
  12. 2
      apps/course/templates/course/course_only_lessons.html
  13. 2
      apps/course/templates/course/lesson.html
  14. 26
      apps/course/views.py
  15. 2
      apps/school/models.py
  16. 2
      apps/school/templates/school/livelessons_list.html
  17. 6
      apps/school/urls.py
  18. 7
      apps/user/admin.py
  19. 22
      apps/user/migrations/0029_emaillog.py
  20. 11
      apps/user/models.py
  21. 4
      apps/user/templates/user/bonus-history.html
  22. 4
      apps/user/templates/user/profile-settings.html
  23. 12
      project/templates/blocks/lil_store_js.html
  24. 8
      project/templates/lilcity/edit_index.html
  25. 1
      requirements.txt
  26. 1
      web/package.json
  27. 3
      web/src/components/Comments.vue
  28. 4
      web/src/components/ContestRedactor.vue
  29. 25
      web/src/components/ContestWorks.vue
  30. 212
      web/src/components/CourseRedactor.vue
  31. 38
      web/src/components/UploadContestWork.vue
  32. 35
      web/src/components/blocks/ContestWork.vue
  33. 20
      web/src/js/app.js
  34. 4
      web/src/js/contest-redactor.js
  35. 9
      web/src/js/modules/api.js
  36. 5
      web/src/js/pages/contest-edit.js
  37. 7
      web/src/js/pages/contest.js
  38. 6
      web/src/js/pages/course-edit.js
  39. 6
      web/src/js/pages/profile.js
  40. 5
      web/src/js/pages/user-gallery-edit.js
  41. 35
      web/src/sass/components/contest-edit.scss
  42. 55
      web/src/sass/components/contest.scss
  43. 166
      web/src/sass/components/course-edit.scss
  44. 166
      web/src/sass/pages/course-edit.scss
  45. 13
      web/webpack.config.js

@ -71,7 +71,7 @@ from apps.payment.models import (
CoursePayment, SchoolPayment, UserBonus, CoursePayment, SchoolPayment, UserBonus,
) )
from apps.school.models import SchoolSchedule, LiveLesson from apps.school.models import SchoolSchedule, LiveLesson
from apps.user.models import AuthorRequest from apps.user.models import AuthorRequest, EmailLog
from project.pusher import pusher from project.pusher import pusher
from project.sengrid import get_sendgrid_client from project.sengrid import get_sendgrid_client
@ -694,6 +694,11 @@ class CaptureEmail(views.APIView):
email = request.data.get('email') email = request.data.get('email')
sg = get_sendgrid_client() sg = get_sendgrid_client()
if not email:
return Response({'error': 'No email'}, status=status.HTTP_400_BAD_REQUEST)
EmailLog.objects.create(email=email, source=EmailLog.SOURCE_TRIAL_LESSON)
# берем все списки # берем все списки
response = sg.client.contactdb.lists.get() response = sg.client.contactdb.lists.get()
if response.status_code != 200: if response.status_code != 200:

@ -8,7 +8,7 @@ from polymorphic.admin import (
from apps.content.models import ( from apps.content.models import (
Baner, Content, Image, Text, ImageText, Video, Baner, Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject, Gallery, GalleryImage, ImageObject,
Contest,ContestWork, Contest, ContestWork, FAQ,
) )
@ -90,3 +90,8 @@ class ContestAdmin(admin.ModelAdmin):
@admin.register(ContestWork) @admin.register(ContestWork)
class ContestWorkAdmin(admin.ModelAdmin): class ContestWorkAdmin(admin.ModelAdmin):
base_model = ContestWork base_model = ContestWork
@admin.register(FAQ)
class FAQAdmin(admin.ModelAdmin):
base_model = FAQ

@ -45,3 +45,8 @@
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}
{% block pre_app_js %}
<script type="text/javascript" src="{% static 'contest.js' %}"></script>
<link rel="stylesheet" href="{% static 'contest.css' %}" />
{% endblock pre_app_js %}

@ -12,7 +12,8 @@
{% block content %} {% block content %}
<contest-redactor {% if object and object.id %}:contest-id="{{ object.id }}"{% endif %}></contest-redactor> <contest-redactor {% if object and object.id %}:contest-id="{{ object.id }}"{% endif %}></contest-redactor>
{% endblock content %} {% endblock content %}
{% block foot %}
<script type="text/javascript" src="{% static 'contestRedactor.js' %}"></script> {% block pre_app_js %}
<link rel="stylesheet" href="{% static 'contestRedactor.css' %}" /> <script type="text/javascript" src="{% static 'contestEdit.js' %}"></script>
{% endblock foot %} <link rel="stylesheet" href="{% static 'contestEdit.css' %}" />
{% endblock pre_app_js %}

@ -0,0 +1,20 @@
from unidecode import unidecode
from django.core.management.base import BaseCommand, CommandError
from django.db.models import Q
from django.utils.text import slugify
from apps.course.models import Course
class Command(BaseCommand):
help = 'Upload users to Roistat'
def handle(self, *args, **options):
courses = Course.objects.filter(Q(slug__isnull=True) | Q(slug=''))
for course in courses:
course.slug = slugify(unidecode(course.title[:90]))
try:
course.save()
except:
course.slug += str(course.id)
course.save()

@ -1,10 +1,13 @@
import arrow import arrow
from random import shuffle from random import shuffle
from uuid import uuid4 from uuid import uuid4
from unidecode import unidecode
from django.db import models from django.db import models
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils import timezone from django.utils import timezone
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.text import slugify
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.conf import settings from django.conf import settings
@ -119,24 +122,24 @@ class Course(BaseModel, DeactivatedMixin):
def __str__(self): def __str__(self):
return str(self.id) + ' ' + self.title return str(self.id) + ' ' + self.title
# def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# if not self.slug: if not self.slug:
# self.slug = slugify( self.slug = slugify(unidecode(self.title[:90]))
# self.title[:90], if self.slug:
# allow_unicode=True if self.slug.isdigit():
# ) self.slug = 'course%s' % self.slug
slug = self.slug
# if Course.objects.filter(slug=self.slug).exclude(id=self.id).exists(): while Course.objects.filter(slug__iexact=self.slug).exclude(id=self.id).exists():
# self.slug += '_' + str(uuid4())[:6] self.slug = '%s_%s' % (slug, str(uuid4())[-4:])
# return super().save() return super().save()
@property @property
def url(self): def url(self):
return self.get_absolute_url() return self.get_absolute_url()
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy('course', args=[self.slug or self.id]) return reverse_lazy('course', args=[self.slug.lower() if self.slug else self.id])
@property @property
def is_free(self): def is_free(self):

@ -53,7 +53,8 @@
{% if course.old_price %} {% if course.old_price %}
<div class="courses__old-price"><s>{{ course.old_price|floatformat:"-2" }}₽</s></div> <div class="courses__old-price"><s>{{ course.old_price|floatformat:"-2" }}₽</s></div>
{% endif %} {% endif %}
<div class="courses__price">{{ course.price|floatformat:"-2" }}₽</div> <div class="courses__price"
{% if course.old_price %}style="color: red;"{% endif %}>{{ course.price|floatformat:"-2" }}₽</div>
{% endif %} {% endif %}
</div> </div>
<a class="courses__title" href="{{ course.url }}">{{ course.title }}</a> <a class="courses__title" href="{{ course.url }}">{{ course.title }}</a>

@ -335,8 +335,7 @@
</div> </div>
<div class="section section_gray"> <div class="section section_gray">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="title">{% if request.user.is_authenticated %}Задавайте вопросы:{% else %}Комментарии:{% endif %}</div>
<div class="title">Задавайте вопросы:</div>
<comments obj-type="course" obj-id="{{ course.id }}"></comments> <comments obj-type="course" obj-id="{{ course.id }}"></comments>
</div> </div>
</div> </div>

@ -7,13 +7,22 @@
Создание {% if live == 'true' %}стрима{% else %}курса{% endif %} Создание {% if live == 'true' %}стрима{% else %}курса{% endif %}
{% endif %} {% endif %}
{% endblock title%} {% endblock title%}
{% block header_buttons %}
{% if live == 'true' %}
<button id="course-redactor__publish-button" class="header__btn btn" disabled>Сохранить</button>
{% else %}
<button id="course-redactor__publish-button" class="header__btn btn" disabled>На модерацию</button>
{% endif %}
{% endblock header_buttons %}
{% block content %} {% block content %}
<course-redactor :live="{{ live }}" author-picture="{% if request.user.photo %}{{ request.user.photo.url }}{% else %}{% static 'img/user_default.jpg' %}{% endif %}" <course-redactor :live="{{ live }}" author-picture="{% if request.user.photo %}{{ request.user.photo.url }}{% else %}{% static 'img/user_default.jpg' %}{% endif %}"
author-name="{{ request.user.first_name }} {{ request.user.last_name }}" author-name="{{ request.user.first_name }} {{ request.user.last_name }}"
access-token="{{ request.user.auth_token }}" access-token="{{ request.user.auth_token }}"
{% if object and object.id %}:course-id="{{ object.id }}"{% endif %}></course-redactor> {% if object and object.id %}:course-id="{{ object.id }}"{% endif %}></course-redactor>
{% endblock content %} {% endblock content %}
{% block foot %} {% block pre_app_js %}
<script type="text/javascript" src="{% static 'courseRedactor.js' %}"></script> <script type="text/javascript" src="{% static 'courseEdit.js' %}"></script>
<link rel="stylesheet" href="{% static 'courseRedactor.css' %}" /> <link rel="stylesheet" href="{% static 'courseEdit.css' %}" />
{% endblock foot %} {% endblock pre_app_js %}

@ -192,7 +192,7 @@
{% endif %} {% endif %}
<div class="section section_gray"> <div class="section section_gray">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="title">Задавайте вопросы:</div> <div class="title">{% if request.user.is_authenticated %}Задавайте вопросы:{% else %}Комментарии:{% endif %}</div>
<comments obj-type="course" obj-id="{{ course.id }}"></comments> <comments obj-type="course" obj-id="{{ course.id }}"></comments>
</div> </div>
</div> </div>

@ -113,7 +113,7 @@
</div> {% endcomment %} </div> {% endcomment %}
<div class="section section_gray"> <div class="section section_gray">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="title">Задавайте вопросы:</div> <div class="title">{% if request.user.is_authenticated %}Задавайте вопросы:{% else %}Комментарии:{% endif %}</div>
<comments obj-type="lesson" obj-id="{{ lesson.id }}"></comments> <comments obj-type="lesson" obj-id="{{ lesson.id }}"></comments>
</div> </div>
</div> </div>

@ -200,6 +200,32 @@ class CourseView(DetailView):
# ((self.object.status != Course.PUBLISHED and request.user.role != User.ADMIN_ROLE) or # ((self.object.status != Course.PUBLISHED and request.user.role != User.ADMIN_ROLE) or
# (self.object.status != Course.PUBLISHED and request.user.role != User.AUTHOR_ROLE and self.object.author != request.user)): # (self.object.status != Course.PUBLISHED and request.user.role != User.AUTHOR_ROLE and self.object.author != request.user)):
def get_object(self, queryset=None):
if queryset is None:
queryset = self.get_queryset()
pk = self.kwargs.get(self.pk_url_kwarg)
slug = self.kwargs.get(self.slug_url_kwarg)
if pk is not None:
queryset = queryset.filter(pk=pk)
if slug is not None and (pk is None or self.query_pk_and_slug):
slug_field = self.get_slug_field()
queryset = queryset.filter(**{'%s__iexact' % slug_field: slug})
if pk is None and slug is None:
raise AttributeError("Generic detail view %s must be called with "
"either an object pk or a slug."
% self.__class__.__name__)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
if self.request.user.is_authenticated: if self.request.user.is_authenticated:

@ -147,7 +147,7 @@ class LiveLesson(BaseModel, DeactivatedMixin):
return self.get_absolute_url() return self.get_absolute_url()
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy('school:lesson-detail', args=[self.date.strftime('%d-%m-%y')]) return reverse_lazy('school:lesson-detail', kwargs={'lesson_date': self.date.strftime('%d-%m-%y')})
def stream_index(self): def stream_index(self):
return self.stream.split('/')[-1] return self.stream.split('/')[-1]

@ -11,7 +11,7 @@
{% for livelesson in livelesson_list %} {% for livelesson in livelesson_list %}
<div class="lessons__item"> <div class="lessons__item">
<div class="lessons__actions lessons__actions__no-hover"> <div class="lessons__actions lessons__actions__no-hover">
<a target="_blank" class="lessons__action" href="{{ live_lesson.url }}"> <a target="_blank" class="lessons__action" href="{{ livelesson.url }}">
<svg class="icon icon-eye"> <svg class="icon icon-eye">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-eye"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-eye"></use>
</svg> </svg>

@ -1,4 +1,4 @@
from django.urls import path, include from django.urls import path, re_path
from .views import ( from .views import (
LiveLessonsView, LiveLessonEditView, LiveLessonsView, LiveLessonEditView,
@ -12,6 +12,6 @@ urlpatterns = [
path('lessons/', LiveLessonsView.as_view(), name='lessons'), path('lessons/', LiveLessonsView.as_view(), name='lessons'),
path('lessons/create', LiveLessonEditView.as_view(), name='lessons-create'), path('lessons/create', LiveLessonEditView.as_view(), name='lessons-create'),
path('lessons/<int:pk>/edit', LiveLessonEditView.as_view(), name='lessons-edit'), path('lessons/<int:pk>/edit', LiveLessonEditView.as_view(), name='lessons-edit'),
path('lessons/<int:pk>/', LiveLessonsDetailView.as_view(), name='lesson-detail'), path('lessons/<int:pk>/', LiveLessonsDetailView.as_view(), name='lesson-detail-id'),
path('<str:lesson_date>', LiveLessonsDetailView.as_view(), name='lesson-detail'), re_path(r'(?P<lesson_date>\d+\-\d+\-\d+)', LiveLessonsDetailView.as_view(), name='lesson-detail'),
] ]

@ -3,7 +3,7 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .models import AuthorRequest, EmailSubscription, SubscriptionCategory from .models import AuthorRequest, EmailSubscription, SubscriptionCategory, EmailLog
User = get_user_model() User = get_user_model()
@ -47,3 +47,8 @@ class EmailSubscriptionAdmin(admin.ModelAdmin):
'user', 'user',
'email', 'email',
) )
@admin.register(EmailLog)
class EmailLogAdmin(admin.ModelAdmin):
pass

@ -0,0 +1,22 @@
# Generated by Django 2.0.7 on 2019-01-25 08:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0028_auto_20181214_0107'),
]
operations = [
migrations.CreateModel(
name='EmailLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(max_length=254, verbose_name='email address')),
('created_at', models.DateTimeField(auto_now_add=True)),
('source', models.PositiveSmallIntegerField(choices=[(1, 'Пробный урок')])),
],
),
]

@ -304,3 +304,14 @@ class Referral(models.Model):
from apps.payment.models import UserBonus from apps.payment.models import UserBonus
UserBonus.objects.create(user=self.referral, amount=referral_bonus, payment=self.payment, referral=self) UserBonus.objects.create(user=self.referral, amount=referral_bonus, payment=self.payment, referral=self)
UserBonus.objects.create(user=self.referrer, amount=referrer_bonus, payment=self.payment, referral=self) UserBonus.objects.create(user=self.referrer, amount=referrer_bonus, payment=self.payment, referral=self)
class EmailLog(models.Model):
SOURCE_TRIAL_LESSON = 1
SOURCE_CHOICES = (
(SOURCE_TRIAL_LESSON, 'Пробный урок'),
)
email = models.EmailField(_('email address'))
created_at = models.DateTimeField(auto_now_add=True)
source = models.PositiveSmallIntegerField(choices=SOURCE_CHOICES)

@ -79,3 +79,7 @@
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}
{% block pre_app_js %}
<script type="text/javascript" src="{% static 'profile.js' %}"></script>
{% endblock pre_app_js %}

@ -323,3 +323,7 @@
// (new Inputmask('+7 (999) 999-99-99')).mask(document.querySelector('[name=phone]')); // (new Inputmask('+7 (999) 999-99-99')).mask(document.querySelector('[name=phone]'));
</script> </script>
{% endblock foot %} {% endblock foot %}
{% block pre_app_js %}
<script type="text/javascript" src="{% static 'profile.js' %}"></script>
{% endblock pre_app_js %}

@ -21,10 +21,14 @@
components: {}, components: {},
urls: { urls: {
courses: "{% url 'courses' %}", courses: "{% url 'courses' %}",
courseEdit: /\/course\/\d+\/edit/,
courseCreate: "{% url 'course_create' %}",
userProfileEdit: "{% url 'user-edit-profile' %}", userProfileEdit: "{% url 'user-edit-profile' %}",
userProfile: "{% url 'user-profile' %}", userProfile: "{% url 'user-profile' %}",
userBonuses: "{% url 'user-bonuses' %}", userBonuses: "{% url 'user-bonuses' %}",
faq: "{% url 'faq' %}", faq: "{% url 'faq' %}",
contestEdit: /contest\/\w+\/edit/,
userGalleryEdit: "{% url 'user-gallery-edit' %}",
}, },
flags: { flags: {
referrer: '{{ referrer.id|default:'' }}', referrer: '{{ referrer.id|default:'' }}',
@ -33,8 +37,12 @@
isGiftCertificateUrl: {{ is_gift_certificate_url|yesno:"true,false" }}, isGiftCertificateUrl: {{ is_gift_certificate_url|yesno:"true,false" }},
}, },
data: {}, data: {},
urlIs: (urlPatternName) => { urlIs: (urlPatternNames) => {
return window.location.pathname.search(window.LIL_STORE.urls[urlPatternName]) > -1; if(! Array.isArray(urlPatternNames)){
urlPatternNames = [urlPatternNames];
}
return urlPatternNames.filter(
urlPatternName => window.location.pathname.search(window.LIL_STORE.urls[urlPatternName]) > -1).length > 0;
}, },
isIndexPage: window.location.pathname == '/', isIndexPage: window.location.pathname == '/',
}; };

@ -53,11 +53,8 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-eye"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-eye"></use>
</svg> </svg>
</button> </button>
{% if live == 'true' %} {% block header_buttons %}
<button id="course-redactor__publish-button" class="header__btn btn" disabled>Сохранить</button> {% endblock header_buttons %}
{% else %}
<button id="course-redactor__publish-button" class="header__btn btn" disabled>На модерацию</button>
{% endif %}
</div> </div>
{% include 'templates/blocks/user_menu.html' %} {% include 'templates/blocks/user_menu.html' %}
</div> </div>
@ -316,6 +313,7 @@
</div> </div>
</div> </div>
{% include 'templates/blocks/lil_store_js.html' %} {% include 'templates/blocks/lil_store_js.html' %}
{% block pre_app_js %}{% endblock pre_app_js %}
<script> <script>
var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }}); var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }});
var schoolAmountForDiscount = parseFloat({{ config.SERVICE_DISCOUNT_MIN_AMOUNT }}); var schoolAmountForDiscount = parseFloat({{ config.SERVICE_DISCOUNT_MIN_AMOUNT }});

@ -36,3 +36,4 @@ short_url
sendgrid sendgrid
drf_dynamic_fields drf_dynamic_fields
flower==0.9.2 flower==0.9.2
unidecode

@ -41,6 +41,7 @@
"baguettebox.js": "^1.10.0", "baguettebox.js": "^1.10.0",
"clipboard": "^2.0.1", "clipboard": "^2.0.1",
"downscale": "^1.0.4", "downscale": "^1.0.4",
"extract-loader": "^3.1.0",
"glob": "^7.1.2", "glob": "^7.1.2",
"history": "^4.7.2", "history": "^4.7.2",
"ilyabirman-likely": "^2.3.0", "ilyabirman-likely": "^2.3.0",

@ -7,6 +7,7 @@
</li> </li>
</ul> </ul>
</div> </div>
<div v-if="!$root.store.user.id && !isChat && noComments">Авторизуйтесь и оставьте первый комментарий!</div>
<comment-form v-if="$root.store.user.id && ! replyTo" :controller="controller"></comment-form> <comment-form v-if="$root.store.user.id && ! replyTo" :controller="controller"></comment-form>
</div> </div>
</template> </template>
@ -26,6 +27,7 @@
nodes: [], nodes: [],
controller: this, controller: this,
flatComments: {}, flatComments: {},
noComments: false,
} }
}, },
methods: { methods: {
@ -119,6 +121,7 @@
.then((response) => { .then((response) => {
vm.loading = false; vm.loading = false;
vm.nodes = response.data; vm.nodes = response.data;
vm.noComments = ! vm.nodes.length;
vm.connectToPusher(); vm.connectToPusher();
}) })
.catch(() => { .catch(() => {

@ -229,7 +229,3 @@
} }
}; };
</script> </script>
<style>
</style>

@ -112,28 +112,3 @@
components: {ContestWork}, components: {ContestWork},
} }
</script> </script>
<style>
.contest-works {
width: 100%;
}
.contest-works__works {
text-align: left;
display: flex;
}
.contest-works__column {
display: flex;
flex-direction: column;
margin-right: 20px;
width: 300px;
}
.contest-works__loader {
width: 100%;
height: 30px;
position: relative;
}
.contest-works__no-works {
text-align: center;
width: 100%;
}
</style>

@ -34,7 +34,8 @@
v-model="course.title" v-model="course.title"
placeholder="Добавить заголовок"></textarea> placeholder="Добавить заголовок"></textarea>
</div> </div>
<div class="courses__content field" v-bind:class="{ error: ($v.course.short_description.$dirty || showErrors) && $v.course.short_description.$invalid }"> <div class="courses__content field" style="width: 300px;"
v-bind:class="{ error: ($v.course.short_description.$dirty || showErrors) && $v.course.short_description.$invalid }">
<vue-redactor :value.sync="course.short_description" placeholder="Добавить краткое описание"/> <vue-redactor :value.sync="course.short_description" placeholder="Добавить краткое описание"/>
</div> </div>
</div> </div>
@ -45,7 +46,7 @@
<div v-if="!live" class="info__field field"> <div v-if="!live" class="info__field field">
<div class="field__label field__label_gray">ССЫЛКА</div> <div class="field__label field__label_gray">ССЫЛКА</div>
<div class="field__wrap"> <div class="field__wrap">
<input type="text" class="field__input" v-model="course.url" @input="slugChanged = true"> <input type="text" class="field__input" v-model="course.slug" @input="slugChanged = true">
</div> </div>
<div class="field__wrap field__wrap--additional">{{ courseFullUrl }}</div> <div class="field__wrap field__wrap--additional">{{ courseFullUrl }}</div>
</div> </div>
@ -273,6 +274,7 @@
price: null, price: null,
old_price: null, old_price: null,
age: 0, age: 0,
slug: '',
url: '', url: '',
coverImage: '', coverImage: '',
kit__body: null, kit__body: null,
@ -473,8 +475,8 @@
}, },
onCourseNameInput() { onCourseNameInput() {
this.$v.course.title.$touch(); this.$v.course.title.$touch();
if (!this.slugChanged) { if (!this.slugChanged && !this.$v.course.status) {
this.course.url = slugify(this.course.title); this.course.slug = (slugify(this.course.title) || '').toLowerCase();
} }
}, },
removeLesson(lessonIndex) { removeLesson(lessonIndex) {
@ -662,7 +664,7 @@
if(this.live) { if(this.live) {
window.location = '/school/lessons'; window.location = '/school/lessons';
} else { } else {
api.publishCourse(this.course.id, this.accessToken) api.publishCourse(this.course, this.accessToken)
.then((response) => { .then((response) => {
window.location = '/course/on-moderation'; window.location = '/course/on-moderation';
}) })
@ -741,24 +743,6 @@
} }
}, },
saveCourseDraft: function (newValue, oldValue) { saveCourseDraft: function (newValue, oldValue) {
//console.log('saveCourseDraft');
// if (!oldValue.id) {
// return;
// }
// if(this.live) {
// if(!this.course.date || this.course.short_description == '' || this.course.title == '') {
// //console.log('live valiedation error');
// return;
// }
// } else {
// if(this.course.short_description == '' || this.course.title == '') {
// //console.log('course validation error');
// return;
// }
// }
if (this.savingDebounceTimeout) { if (this.savingDebounceTimeout) {
clearTimeout(this.savingDebounceTimeout); clearTimeout(this.savingDebounceTimeout);
} }
@ -771,13 +755,14 @@
this.courseSaving = true; this.courseSaving = true;
this.changeSavingStatus(); this.changeSavingStatus();
const courseObject = this.course; const courseObject = this.course;
courseObject.url = (courseObject.url) ? slugify(courseObject.url):courseObject.url; courseObject.slug = courseObject.slug && slugify(courseObject.slug);
api.saveCourse(courseObject, this.accessToken) api.saveCourse(courseObject, this.accessToken)
.then((response) => { .then((response) => {
this.courseSaving = false; this.courseSaving = false;
this.changeSavingStatus(true); this.changeSavingStatus(true);
this.courseSyncHook = true; this.courseSyncHook = true;
const courseData = api.convertCourseJson(response.data); const courseData = api.convertCourseJson(response.data);
this.course.slug = courseData.slug;
if (this.course.coverImage) { if (this.course.coverImage) {
courseData.coverImage = this.course.coverImage; courseData.coverImage = this.course.coverImage;
} }
@ -804,19 +789,11 @@
}) })
} }
}); });
if (courseData.url) {
this.slugChanged = true;
}
if(courseData.id) { if(courseData.id) {
this.course.id = courseData.id; this.course.id = courseData.id;
} }
/*if(this.live && courseData.date) {
this.course.date = _.find(this.scheduleOptions, function(item){
return item.value == courseData.date;
});
}*/
this.$nextTick(() => { this.$nextTick(() => {
this.courseSyncHook = false; this.courseSyncHook = false;
}); });
@ -1029,11 +1006,8 @@
} }
}, },
courseFullUrl() { courseFullUrl() {
if (!this.course.url) { let suffix = this.course.slug || this.course.id || 'ваша_ссылка';
return `https://lil.city/course/${this.course.id}`; return `https://lil.school/course/${suffix}`;
}
let suffix = this.course.url ? this.course.url : 'ваша_ссылка';
return `https://lil.city/course/${suffix}`;
}, },
}, },
beforeDestroy() { beforeDestroy() {
@ -1074,168 +1048,4 @@
} }
</script> </script>
<style lang="scss">
.vdp-datepicker__calendar {
width: 240px;
margin-top: 10px;
padding: 5px;
background: white;
box-shadow: 0 2px 20px 0 rgba(0, 0, 0, 0.1);
z-index: 99 !important;
header {
display: flex;
margin-bottom: 5px;
-ms-flex-align: center;
align-items: center;
}
.prev, .next {
font-size: 0;
cursor: pointer;
order: 1;
width: auto !important;
padding: 10px;
}
.prev {
order: 1;
}
.next {
order: 3;
}
.prev:before, .next:before {
content: '';
display: block;
width: 10px;
height: 10px;
border: solid #E6E6E6;
border-width: 2px 2px 0 0;
}
.prev:after, .next:after {
content: none !important;
}
.prev:before {
transform: rotate(-135deg);
}
.next:before {
transform: rotate(45deg);
}
}
.kit__preview {
img {
width: 140px;
height: 140px;
}
}
.kit__photo {
width: 140px;
height: 140px;
}
.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;
}
.course-redactor__preview-button-bg-save {
background-color: #58fffb;
}
.course-redactor__preview-button {
transition: backgroundColor 0.5s ease-in-out;
}
.field_text {
height: 270px;
overflow: scroll;
}
.courses__item {
flex: 0 0 300px;
}
.courses__item .field {
margin-bottom: 0;
}
.courses__content .redactor-box {
overflow-x: visible;
overflow-y: auto;
max-height: 200px;
background: none;
margin-top: 10px;
}
.courses__content .redactor-layer{
background: none;
}
.courses__theme {
flex: 1;
}
.courses__preview {
.upload {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.upload__title {
color: #888888;
font-size: 16px;
margin-top: 100px;
width: 100%;
text-align: center;
}
.upload__file {
width: 100%;
height: 100%;
}
}
.course-delete-cover {
left: 5px;
position: absolute;
bottom: 3px;
}
.field-category .select__head {
font-size: inherit;
line-height: inherit;
height: auto;
}
.datetime-fields {
display: flex;
}
.field-time {
margin-left: 10px;
flex: 50%;
}
</style>

@ -153,41 +153,3 @@
} }
} }
</script> </script>
<style lang="scss">
.upload-contest-work {
.popup__wrap {
padding: 35px 35px 0;
}
.title {
text-align: center; font-size: 24px;
.text__curve {
right: 55px;
width: 170px;
bottom: -40px;
}
}
.kit__photo {
height: 400px;
}
.kit__photo.has-image {
border: none;
}
.kit__photo-image {
max-height: 400px;
height: auto;
width: auto;
}
.kit__file {
bottom: 0;
}
}
</style>

@ -25,38 +25,3 @@
components: {Likes}, components: {Likes},
} }
</script> </script>
<style lang="scss">
.contest-work-item {
break-inside: avoid;
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
transition: opacity .4s ease-in-out;
text-transform: uppercase;
font-weight: bold;
color: black;
border: 1px solid #ececec;
display: block;
}
.contest-work-item__img {
width: 100%;
height: auto;
}
.contest-work-item__info {
display: flex;
padding: 5px 10px;
}
.contest-work-item__age {
color: #919191;
}
.contest-work-item__bio {
flex: calc(100% - 70px);
}
@media only screen and (min-width: 1023px) {
.contest-works:hover .contest-work-item:not(:hover) {
opacity: 0.4;
}
}
</style>

@ -13,7 +13,6 @@ import "./modules/tabs";
import "./modules/popup"; import "./modules/popup";
import "./modules/courses"; import "./modules/courses";
import "./modules/comments"; import "./modules/comments";
import "./modules/comments";
import "./modules/password-show"; import "./modules/password-show";
import "./modules/notification"; import "./modules/notification";
import "./modules/mixpanel"; import "./modules/mixpanel";
@ -25,9 +24,8 @@ import Vue from 'vue';
import Vuelidate from 'vuelidate'; import Vuelidate from 'vuelidate';
import VueAutosize from '../components/directives/autosize' import VueAutosize from '../components/directives/autosize'
import Comments from '../components/Comments'; import Comments from '../components/Comments';
import UploadContestWork from '../components/UploadContestWork.vue';
import ContestWorks from '../components/ContestWorks.vue';
import Likes from '../components/blocks/Likes.vue'; import Likes from '../components/blocks/Likes.vue';
import FAQ from '../components/FAQ.vue';
Vue.use(Vuelidate); Vue.use(Vuelidate);
Vue.use(VueAutosize); Vue.use(VueAutosize);
@ -38,23 +36,13 @@ if (process.env.NODE_ENV === 'development') {
} }
const components = { const components = {
UploadContestWork, 'likes': Likes,
ContestWorks, 'comments': Comments,
Likes, 'faq': FAQ,
Comments,
}; };
Object.assign(components, window.LIL_STORE.components); Object.assign(components, window.LIL_STORE.components);
if(window.LIL_STORE.urlIs('faq')){
const FAQ = require('../components/FAQ.vue');
components['faq'] = FAQ.default;
}
if(window.LIL_STORE.urlIs('userProfileEdit') || window.LIL_STORE.urlIs('userBonuses')){
const profile = require("./modules/profile");
profile.main();
}
const app = new Vue({ const app = new Vue({
el: '#lilcity-vue-app', el: '#lilcity-vue-app',
data() { data() {

@ -1,4 +0,0 @@
import ContestRedactor from '../components/ContestRedactor.vue'
window.LIL_STORE.components['contest-redactor'] = ContestRedactor;

@ -113,7 +113,7 @@ export const api = {
deferred_start_at: deferredStart, deferred_start_at: deferredStart,
duration: courseObject.duration || 0, duration: courseObject.duration || 0,
is_featured: courseObject.is_featured, is_featured: courseObject.is_featured,
slug: courseObject.url, slug: (courseObject.slug || '').toLowerCase(),
date: (courseObject.date) ? courseObject.date.value:null, date: (courseObject.date) ? courseObject.date.value:null,
stream: courseObject.stream, stream: courseObject.stream,
cover: courseObject.coverImageId ? courseObject.coverImageId : null, cover: courseObject.coverImageId ? courseObject.coverImageId : null,
@ -194,7 +194,7 @@ export const api = {
time: deferredTime, time: deferredTime,
duration: courseJSON.duration, duration: courseJSON.duration,
is_featured: courseJSON.is_featured, is_featured: courseJSON.is_featured,
url: courseJSON.slug, slug: courseJSON.slug,
stream: courseJSON.stream, stream: courseJSON.stream,
coverImageId: courseJSON.cover && courseJSON.cover.id ? courseJSON.cover.id : null, coverImageId: courseJSON.cover && courseJSON.cover.id ? courseJSON.cover.id : null,
coverImage: courseJSON.cover && courseJSON.cover.image ? courseJSON.cover.image : null, coverImage: courseJSON.cover && courseJSON.cover.image ? courseJSON.cover.image : null,
@ -437,8 +437,9 @@ export const api = {
} }
}); });
}, },
publishCourse: (courseId, accessToken) => { publishCourse: (course, accessToken) => {
return api.patch(`/api/v1/courses/${courseId}/`, {status: 1}, { course.status = 1;
return api.patch(`/api/v1/courses/${course.id}/`, course, {
headers: { headers: {
'Authorization': `Token ${accessToken}`, 'Authorization': `Token ${accessToken}`,
} }

@ -0,0 +1,5 @@
import '../../sass/components/contest-edit.scss';
import ContestRedactor from '../../components/ContestRedactor.vue';
window.LIL_STORE.components['contest-redactor'] = ContestRedactor;

@ -0,0 +1,7 @@
import '../../sass/components/contest.scss';
import UploadContestWork from '../../components/UploadContestWork.vue';
import ContestWorks from '../../components/ContestWorks.vue';
window.LIL_STORE.components['upload-contest-work'] = UploadContestWork;
window.LIL_STORE.components['contest-works'] = ContestWorks;

@ -1,5 +1,7 @@
import 'babel-polyfill' import '../../sass/components/course-edit.scss';
import CourseRedactor from '../components/CourseRedactor.vue'
import 'babel-polyfill';
import CourseRedactor from '../../components/CourseRedactor.vue';
import $ from 'jquery'; import $ from 'jquery';
window.LIL_STORE.components['course-redactor'] = CourseRedactor; window.LIL_STORE.components['course-redactor'] = CourseRedactor;

@ -1,9 +1,9 @@
import $ from 'jquery'; import $ from 'jquery';
import slugify from 'slugify'; import slugify from 'slugify';
import ClipboardJS from 'clipboard'; import ClipboardJS from 'clipboard';
import {showNotification} from './notification'; import {showNotification} from '../modules/notification';
export const main = () => { $(document).ready(function () {
if(window.LIL_STORE.urlIs('userBonuses')){ if(window.LIL_STORE.urlIs('userBonuses')){
$('#referrer-url').select().click(function(){ $('#referrer-url').select().click(function(){
$(this).select(); $(this).select();
@ -53,4 +53,4 @@ export const main = () => {
}); });
changeSlug(); changeSlug();
} }
} });

@ -1,6 +1,7 @@
import BlockImages from '../components/blocks/BlockImages.vue'; import BlockImages from '../../components/blocks/BlockImages.vue';
import $ from 'jquery'; import $ from 'jquery';
import {api} from "./modules/api"; import {api} from "../modules/api";
window.LIL_STORE.components['block-images'] = BlockImages; window.LIL_STORE.components['block-images'] = BlockImages;

@ -0,0 +1,35 @@
.upload-contest-work {
.popup__wrap {
padding: 35px 35px 0;
}
.title {
text-align: center; font-size: 24px;
.text__curve {
right: 55px;
width: 170px;
bottom: -40px;
}
}
.kit__photo {
height: 400px;
}
.kit__photo.has-image {
border: none;
}
.kit__photo-image {
max-height: 400px;
height: auto;
width: auto;
}
.kit__file {
bottom: 0;
}
}

@ -0,0 +1,55 @@
.contest-works {
width: 100%;
}
.contest-works__works {
text-align: left;
display: flex;
}
.contest-works__column {
display: flex;
flex-direction: column;
margin-right: 20px;
width: 300px;
}
.contest-works__loader {
width: 100%;
height: 30px;
position: relative;
}
.contest-works__no-works {
text-align: center;
width: 100%;
}
.contest-work-item {
break-inside: avoid;
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
transition: opacity .4s ease-in-out;
text-transform: uppercase;
font-weight: bold;
color: black;
border: 1px solid #ececec;
display: block;
}
.contest-work-item__img {
width: 100%;
height: auto;
}
.contest-work-item__info {
display: flex;
padding: 5px 10px;
}
.contest-work-item__age {
color: #919191;
}
.contest-work-item__bio {
flex: calc(100% - 70px);
}
@media only screen and (min-width: 1023px) {
.contest-works:hover .contest-work-item:not(:hover) {
opacity: 0.4;
}
}

@ -0,0 +1,166 @@
.vdp-datepicker__calendar {
width: 240px;
margin-top: 10px;
padding: 5px;
background: white;
box-shadow: 0 2px 20px 0 rgba(0, 0, 0, 0.1);
z-index: 99 !important;
header {
display: flex;
margin-bottom: 5px;
-ms-flex-align: center;
align-items: center;
}
.prev, .next {
font-size: 0;
cursor: pointer;
order: 1;
width: auto !important;
padding: 10px;
}
.prev {
order: 1;
}
.next {
order: 3;
}
.prev:before, .next:before {
content: '';
display: block;
width: 10px;
height: 10px;
border: solid #E6E6E6;
border-width: 2px 2px 0 0;
}
.prev:after, .next:after {
content: none !important;
}
.prev:before {
transform: rotate(-135deg);
}
.next:before {
transform: rotate(45deg);
}
}
.kit__preview {
img {
width: 140px;
height: 140px;
}
}
.kit__photo {
width: 140px;
height: 140px;
}
.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;
}
.course-redactor__preview-button-bg-save {
background-color: #58fffb;
}
.course-redactor__preview-button {
transition: backgroundColor 0.5s ease-in-out;
}
.field_text {
height: 270px;
overflow: scroll;
}
.courses__item {
flex: 0 0 300px;
}
.courses__item .field {
margin-bottom: 0;
}
.courses__content .redactor-box {
overflow-x: visible;
overflow-y: auto;
max-height: 200px;
background: none;
margin-top: 10px;
}
.courses__content .redactor-layer{
background: none;
}
.courses__theme {
flex: 1;
}
.courses__price {
margin-left: 20px;
}
.courses__preview {
.upload {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.upload__title {
color: #888888;
font-size: 16px;
margin-top: 100px;
width: 100%;
text-align: center;
}
.upload__file {
width: 100%;
height: 100%;
}
}
.course-delete-cover {
left: 5px;
position: absolute;
bottom: 3px;
}
.field-category .select__head {
font-size: inherit;
line-height: inherit;
height: auto;
}
.datetime-fields {
display: flex;
}
.field-time {
margin-left: 10px;
flex: 50%;
}

@ -0,0 +1,166 @@
.vdp-datepicker__calendar {
width: 240px;
margin-top: 10px;
padding: 5px;
background: white;
box-shadow: 0 2px 20px 0 rgba(0, 0, 0, 0.1);
z-index: 99 !important;
header {
display: flex;
margin-bottom: 5px;
-ms-flex-align: center;
align-items: center;
}
.prev, .next {
font-size: 0;
cursor: pointer;
order: 1;
width: auto !important;
padding: 10px;
}
.prev {
order: 1;
}
.next {
order: 3;
}
.prev:before, .next:before {
content: '';
display: block;
width: 10px;
height: 10px;
border: solid #E6E6E6;
border-width: 2px 2px 0 0;
}
.prev:after, .next:after {
content: none !important;
}
.prev:before {
transform: rotate(-135deg);
}
.next:before {
transform: rotate(45deg);
}
}
.kit__preview {
img {
width: 140px;
height: 140px;
}
}
.kit__photo {
width: 140px;
height: 140px;
}
.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;
}
.course-redactor__preview-button-bg-save {
background-color: #58fffb;
}
.course-redactor__preview-button {
transition: backgroundColor 0.5s ease-in-out;
}
.field_text {
height: 270px;
overflow: scroll;
}
.courses__item {
flex: 0 0 300px;
}
.courses__item .field {
margin-bottom: 0;
}
.courses__content .redactor-box {
overflow-x: visible;
overflow-y: auto;
max-height: 200px;
background: none;
margin-top: 10px;
}
.courses__content .redactor-layer{
background: none;
}
.courses__theme {
flex: 1;
}
.courses__price {
margin-left: 20px;
}
.courses__preview {
.upload {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.upload__title {
color: #888888;
font-size: 16px;
margin-top: 100px;
width: 100%;
text-align: center;
}
.upload__file {
width: 100%;
height: 100%;
}
}
.course-delete-cover {
left: 5px;
position: absolute;
bottom: 3px;
}
.field-category .select__head {
font-size: inherit;
line-height: inherit;
height: auto;
}
.datetime-fields {
display: flex;
}
.field-time {
margin-left: 10px;
flex: 50%;
}

@ -5,26 +5,27 @@ const NODE_ENV = process.env.NODE_ENV || 'development';
const ExtractTextPlugin = require("extract-text-webpack-plugin"); const ExtractTextPlugin = require("extract-text-webpack-plugin");
const SpriteLoaderPlugin = require('svg-sprite-loader/plugin'); const SpriteLoaderPlugin = require('svg-sprite-loader/plugin');
module.exports = { module.exports = {
entry: { entry: {
app: "./src/js/app.js", app: "./src/js/app.js",
courseRedactor: "./src/js/course-redactor.js", courseEdit: "./src/js/pages/course-edit.js",
contestRedactor: "./src/js/contest-redactor.js", contest: "./src/js/pages/contest.js",
userGalleryEdit: "./src/js/user-gallery-edit.js", contestEdit: "./src/js/pages/contest-edit.js",
profile: "./src/js/pages/profile.js",
userGalleryEdit: "./src/js/pages/user-gallery-edit.js",
mixpanel: "./src/js/third_party/mixpanel-2-latest.js", mixpanel: "./src/js/third_party/mixpanel-2-latest.js",
sprite: glob('./src/icons/*.svg'), sprite: glob('./src/icons/*.svg'),
images: glob('./src/img/*.*'), images: glob('./src/img/*.*'),
imagesCertificates: glob('./src/img/user-certificates/*'), imagesCertificates: glob('./src/img/user-certificates/*'),
imagesGiftCertificates: glob('./src/img/gift-certificates/*'), imagesGiftCertificates: glob('./src/img/gift-certificates/*'),
imagesReviews: glob('./src/img/reviews/*'), imagesReviews: glob('./src/img/reviews/*'),
fonts: glob('./src/fonts/*') fonts: glob('./src/fonts/*'),
}, },
output: { output: {
path: path.join(__dirname, "build"), path: path.join(__dirname, "build"),
filename: NODE_ENV === 'development' ? '[name].js' : '[name].js', filename: NODE_ENV === 'development' ? '[name].js' : '[name].js',
library: '[name]', library: '[name]',
//filename: NODE_ENV === 'development' ? '[name].js' : '[name].[id].[chunkhash].js',
//library: '[name]',
publicPath: '/static/', publicPath: '/static/',
}, },
module: { module: {

Loading…
Cancel
Save