Новый дизайн / Курсы поиск

remotes/origin/features/course-search-16-08-19
gzbender 7 years ago
parent 5407ccebb5
commit f10cd4e6b5
  1. 12
      api/v1/serializers/course.py
  2. 3
      api/v1/urls.py
  3. 9
      api/v1/views.py
  4. 38
      apps/course/migrations/0050_auto_20190815_1537.py
  5. 24
      apps/course/models.py
  6. 1
      apps/course/templates/course/_items.html
  7. 39
      apps/course/templates/course/courses.html
  8. 23
      apps/course/views.py
  9. 11
      web/package.json
  10. 56
      web/src/components/CourseRedactor.vue
  11. 120
      web/src/sass/_common.sass

@ -9,7 +9,7 @@ from apps.course.models import (
Comment, CourseComment, LessonComment, Comment, CourseComment, LessonComment,
Material, Lesson, Material, Lesson,
Like, Like,
LiveLessonComment) LiveLessonComment, Tag)
from .content import ( from .content import (
ImageObjectSerializer, ContentSerializer, ContentCreateSerializer, ImageObjectSerializer, ContentSerializer, ContentCreateSerializer,
GallerySerializer, ) GallerySerializer, )
@ -19,6 +19,12 @@ from .user import UserSerializer
User = get_user_model() User = get_user_model()
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ('tag',)
class MaterialCreateSerializer(serializers.ModelSerializer): class MaterialCreateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
@ -116,6 +122,7 @@ class CourseCreateSerializer(DispatchContentMixin,
) )
materials = MaterialSerializer(many=True, required=False) materials = MaterialSerializer(many=True, required=False)
gallery = GallerySerializer() gallery = GallerySerializer()
tags = TagSerializer(many=True, required=False)
class Meta: class Meta:
model = Course model = Course
@ -145,6 +152,7 @@ class CourseCreateSerializer(DispatchContentMixin,
'content', 'content',
'gallery', 'gallery',
'lessons', 'lessons',
'tags',
) )
read_only_fields = ( read_only_fields = (
@ -269,6 +277,7 @@ class CourseSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
gallery = GallerySerializer() gallery = GallerySerializer()
content = ContentSerializer(many=True) content = ContentSerializer(many=True)
lessons = LessonSerializer(many=True) lessons = LessonSerializer(many=True)
tags = TagSerializer(many=True, required=False)
class Meta: class Meta:
model = Course model = Course
@ -298,6 +307,7 @@ class CourseSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
'content', 'content',
'gallery', 'gallery',
'lessons', 'lessons',
'tags',
) )
read_only_fields = ( read_only_fields = (

@ -19,7 +19,7 @@ from .views import (
SchoolScheduleViewSet, LiveLessonViewSet, SchoolScheduleViewSet, LiveLessonViewSet,
PaymentViewSet, ObjectCommentsViewSet, PaymentViewSet, ObjectCommentsViewSet,
ContestViewSet, ContestWorkViewSet, NotifiedAboutBonuses, ContestViewSet, ContestWorkViewSet, NotifiedAboutBonuses,
AuthorBalanceUsersViewSet, CaptureEmail, FAQViewSet, UserGalleryViewSet, BonusesViewSet) AuthorBalanceUsersViewSet, CaptureEmail, FAQViewSet, UserGalleryViewSet, BonusesViewSet, TagViewSet)
router = DefaultRouter() router = DefaultRouter()
router.register(r'author-requests', AuthorRequestViewSet, base_name='author-requests') router.register(r'author-requests', AuthorRequestViewSet, base_name='author-requests')
@ -48,6 +48,7 @@ router.register(r'users', UserViewSet, base_name='users')
router.register(r'user-gallery', UserGalleryViewSet, base_name='user-gallery') router.register(r'user-gallery', UserGalleryViewSet, base_name='user-gallery')
router.register(r'contests', ContestViewSet, base_name='contests') router.register(r'contests', ContestViewSet, base_name='contests')
router.register(r'contest-works', ContestWorkViewSet, base_name='contest_works') router.register(r'contest-works', ContestWorkViewSet, base_name='contest_works')
router.register(r'tags', TagViewSet, base_name='tags')
# router.register(r'configs', ConfigViewSet, base_name='configs') # router.register(r'configs', ConfigViewSet, base_name='configs')

@ -21,7 +21,7 @@ from .serializers.course import (
MaterialSerializer, MaterialCreateSerializer, MaterialSerializer, MaterialCreateSerializer,
LessonSerializer, LessonCreateSerializer, LessonSerializer, LessonCreateSerializer,
LikeCreateSerializer, CourseCommentSerializer, LessonCommentSerializer, LikeCreateSerializer, CourseCommentSerializer, LessonCommentSerializer,
LiveLessonCommentSerializer,) LiveLessonCommentSerializer, TagSerializer)
from .serializers.content import ( from .serializers.content import (
BannerSerializer, BannerSerializer,
ImageSerializer, ImageCreateSerializer, ImageSerializer, ImageCreateSerializer,
@ -60,7 +60,7 @@ from apps.course.models import (
Comment, CourseComment, LessonComment, Comment, CourseComment, LessonComment,
Material, Lesson, Material, Lesson,
Like, Like,
LiveLessonComment) LiveLessonComment, Tag)
from apps.config.models import Config from apps.config.models import Config
from apps.content.models import ( from apps.content.models import (
Banner, Image, Text, ImageText, Video, Banner, Image, Text, ImageText, Video,
@ -774,3 +774,8 @@ class NotifiedAboutBonuses(views.APIView):
b.save() b.save()
return Response({'status': 'ok'}) return Response({'status': 'ok'})
class TagViewSet(ExtendedModelViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer
search_fields = ('tag',)

@ -0,0 +1,38 @@
# Generated by Django 2.0.7 on 2019-08-15 15:37
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('course', '0049_auto_20190207_1551'),
]
operations = [
migrations.CreateModel(
name='CourseTags',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('course_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.Course')),
],
),
migrations.CreateModel(
name='Tag',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tag', models.CharField(max_length=20)),
],
),
migrations.AddField(
model_name='coursetags',
name='tag_id',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.Tag'),
),
migrations.AddField(
model_name='course',
name='tags',
field=models.ManyToManyField(blank=True, through='course.CourseTags', to='course.Tag'),
),
]

@ -21,6 +21,17 @@ from apps.content.models import ImageObject, Gallery, Video, ContestWork
User = get_user_model() User = get_user_model()
def default_slug():
return str(uuid4())
def deferred_start_at_validator(value):
if value < now():
raise ValidationError(
'Дата и время начала курса не может быть меньше текущих.',
)
class Like(models.Model): class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
ip = models.GenericIPAddressField(blank=True, null=True) ip = models.GenericIPAddressField(blank=True, null=True)
@ -29,15 +40,13 @@ class Like(models.Model):
update_at = models.DateTimeField(auto_now=True) update_at = models.DateTimeField(auto_now=True)
def default_slug(): class Tag(models.Model):
return str(uuid4()) tag = models.CharField(max_length=20,)
def deferred_start_at_validator(value): class CourseTags(models.Model):
if value < now(): tag_id = models.ForeignKey(Tag, on_delete=models.CASCADE)
raise ValidationError( course_id = models.ForeignKey('Course', on_delete=models.CASCADE)
'Дата и время начала курса не может быть меньше текущих.',
)
class Course(BaseModel, DeactivatedMixin): class Course(BaseModel, DeactivatedMixin):
@ -110,6 +119,7 @@ class Course(BaseModel, DeactivatedMixin):
on_delete=models.CASCADE, null=True, blank=True, on_delete=models.CASCADE, null=True, blank=True,
related_name='results_gallery', related_name='results_gallery',
) )
tags = models.ManyToManyField('Tag', through=CourseTags, blank=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True) update_at = models.DateTimeField(auto_now=True)

@ -13,7 +13,6 @@
{% empty %} {% empty %}
<img class="courses__pic" src="{% static 'img/no_cover.png' %}" width="300px" /> <img class="courses__pic" src="{% static 'img/no_cover.png' %}" width="300px" />
{% endthumbnail %} {% endthumbnail %}
<div class="courses__view">Подробнее</div>
{% if course.is_featured %} {% if course.is_featured %}
<div class="courses__label courses__label_fav"></div> <div class="courses__label courses__label_fav"></div>
{% endif %} {% endif %}

@ -12,17 +12,40 @@
</div> </div>
{% endif %} {% endif %}
<div class="section"> <div class="section">
<div class="section__center center course-search">
<form class="course-search__form" action="." method="get">
{% if cat %}<input type="hidden" name="cat" value="{{ cat.0|default:'' }}" />
{% else %}
<input type="hidden" name="category" value="{% if category %}{{ category.id }}{% endif %}" />
{% endif %}
<input type="hidden" name="age" value="{{ age.0|default:'' }}" />
<input name="q" class="course-search__query" type="text" value="{{ search_query|default:'' }}" />
<button class="btn course-search__search" type="submit">
<svg class="icon icon-search">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-search"></use>
</svg>
</button>
</form>
{% if tags|length %}
<div class="course-search__text">
ИСКАТЬ ПО ТЕГАМ
</div>
<div class="course-search__tags">
{% for tag in tags %}
<a class="course-search__tag tag">#{{ tag.tag }}</a>
{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="section" style="padding-top: 5px">
<div class="section__center center"> <div class="section__center center">
<div class="text text_lg">
<p>Учите и развивайте креативное мышление когда и где угодно. Если вам не совсем удобно заниматься с нами в прямом эфире каждый день, как в
нашей онлайн-школе, специально для вас мы делаем отдельные уроки в записи, которые вы можете проходить,
когда вам будет удобно.</p><img class="text__curve text__curve_four" src="{% static 'img/curve-4.svg' %}" width="155"></div>
<div class="head"> <div class="head">
<div class="head__title title title_center">Курсы</div> <div class="head__title title">Курсы</div>
<div class="head__right"> <div class="head__right courses-filter">
<div class="head__field field"> <div class="head__field field">
<div class="field__wrap"> <div class="field__wrap">
<div class="field__select select js-select{% if category %} selected{% endif %}" data-category-select> <div class="courses-filter__select field__select select js-select{% if category %} selected{% endif %}" data-category-select>
<div class="select__head js-select-head">Категории</div> <div class="select__head js-select-head">Категории</div>
<div class="select__drop js-select-drop"> <div class="select__drop js-select-drop">
<div class="select__option js-select-option{% if not category.0 %} active{% endif %}" data-category-option> <div class="select__option js-select-option{% if not category.0 %} active{% endif %}" data-category-option>
@ -35,7 +58,7 @@
</div> </div>
<div class="head__field field"> <div class="head__field field">
<div class="field__wrap"> <div class="field__wrap">
<div class="field__select select js-select{% if age.0 %} selected{% endif %}"> <div class="courses-filter__select field__select select js-select{% if age.0 %} selected{% endif %}">
<div class="select__head js-select-head">{% if age_name %}{{ age_name }}{% else %}Возраст{% endif %}</div> <div class="select__head js-select-head">{% if age_name %}{{ age_name }}{% else %}Возраст{% endif %}</div>
<div class="select__drop js-select-drop"> <div class="select__drop js-select-drop">
<div class="select__option js-select-option{% if not age.0 %} active{% endif %}" data-age-option> <div class="select__option js-select-option{% if not age.0 %} active{% endif %}" data-age-option>

@ -1,4 +1,5 @@
from datetime import timedelta from datetime import timedelta
from itertools import groupby
from paymentwall import Pingback from paymentwall import Pingback
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -17,8 +18,9 @@ from django.utils.timezone import now
from apps.content.models import Banner from apps.content.models import Banner
from apps.payment.models import AuthorBalance, CoursePayment from apps.payment.models import AuthorBalance, CoursePayment
from .models import Course, Like, Lesson, CourseComment, LessonComment, Category from .models import Course, Like, Lesson, CourseComment, LessonComment, Category, CourseTags, Tag
from .filters import CourseFilter from .filters import CourseFilter
from project.utils.db import ModelFieldsNames, format_sql, execute_sql
User = get_user_model() User = get_user_model()
@ -312,6 +314,14 @@ class CoursesView(ListView):
).prefetch_related( ).prefetch_related(
'likes', 'materials', 'content', 'likes', 'materials', 'content',
).filter(status=Course.PUBLISHED) ).filter(status=Course.PUBLISHED)
q = self.request.GET.get('q')
if q:
if q.startswith('#'):
queryset = queryset.filter(tags__tag__istartswith=q[1:])
else:
queryset = queryset.filter(Q(tags__tag__icontains=q) | Q(title__icontains=q) | Q(short_description__icontains=q)
| Q(author__first_name__icontains=q) | Q(author__last_name__icontains=q)
| Q(author__email__icontains=q))
filtered = CourseFilter(self.request.GET, queryset=queryset) filtered = CourseFilter(self.request.GET, queryset=queryset)
return filtered.qs return filtered.qs
@ -319,6 +329,17 @@ class CoursesView(ListView):
context = super().get_context_data() context = super().get_context_data()
filtered = CourseFilter(self.request.GET) filtered = CourseFilter(self.request.GET)
context.update(filtered.data) context.update(filtered.data)
sql = format_sql('''
select {ct.tag_id}
from {ct}
group by {ct.tag_id}
order by count(*) desc
limit 15''', ct=CourseTags)
tags = Tag.objects.filter(id__in=execute_sql(sql)).order_by('tag')
print('tags', tags)
context['tags'] = map(lambda i: i[0], sorted(tags, key=lambda i: len(i[1]))[:15])
print("context['tags']", context['tags'])
context['search_query'] = self.request.GET.get('q', '')
context['banners'] = Banner.get_for_page(Banner.PAGE_COURSES) context['banners'] = Banner.get_for_page(Banner.PAGE_COURSES)
context['course_items'] = Course.shuffle(context.get('course_items')) context['course_items'] = Course.shuffle(context.get('course_items'))
context['ages'] = Course.AGE_CHOICES[1:] context['ages'] = Course.AGE_CHOICES[1:]

@ -14,14 +14,14 @@
"babel-plugin-transform-runtime": "^6.23.0", "babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.6.1",
"babel-preset-es2015": "^6.24.1", "babel-preset-es2015": "^6.24.1",
"browser-sync": "^2.10.0", "browser-sync": "^2.26.7",
"css-loader": "^0.28.9", "css-loader": "^0.28.9",
"css-mqpacker": "^5.0.1", "css-mqpacker": "^5.0.1",
"del": "^2.2.0", "del": "^2.2.0",
"extract-text-webpack-plugin": "^3.0.2", "extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.6", "file-loader": "^1.1.6",
"lodash": "^4.3.0", "lodash": "^4.17.15",
"node-sass": "^4.9.0", "node-sass": "^4.12.0",
"require-dir": "^0.3.0", "require-dir": "^0.3.0",
"run-sequence": "^1.1.5", "run-sequence": "^1.1.5",
"sass-loader": "^7.0.1", "sass-loader": "^7.0.1",
@ -36,7 +36,7 @@
"dependencies": { "dependencies": {
"autosize": "^4.0.2", "autosize": "^4.0.2",
"autosize-input": "^1.0.2", "autosize-input": "^1.0.2",
"axios": "^0.17.1", "axios": "^0.19.0",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"baguettebox.js": "^1.10.0", "baguettebox.js": "^1.10.0",
"bowser": "^2.1.2", "bowser": "^2.1.2",
@ -47,7 +47,7 @@
"history": "^4.7.2", "history": "^4.7.2",
"ilyabirman-likely": "^2.3.0", "ilyabirman-likely": "^2.3.0",
"inputmask": "^3.3.11", "inputmask": "^3.3.11",
"jquery": "^3.3.1", "jquery": "^3.4.1",
"js-cookie": "^2.2.0", "js-cookie": "^2.2.0",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"modal-video": "git+https://github.com/gzbender/modal-video.git", "modal-video": "git+https://github.com/gzbender/modal-video.git",
@ -62,6 +62,7 @@
"vue": "^2.5.13", "vue": "^2.5.13",
"vue-autosize": "^1.0.2", "vue-autosize": "^1.0.2",
"vue-awesome-swiper": "^3.1.3", "vue-awesome-swiper": "^3.1.3",
"vue-tags-component": "^1.3.0",
"vuedraggable": "^2.16.0", "vuedraggable": "^2.16.0",
"vuejs-datepicker": "^0.9.25", "vuejs-datepicker": "^0.9.25",
"vuelidate": "^0.6.1" "vuelidate": "^0.6.1"

@ -123,6 +123,20 @@
<input type="checkbox" class="switch__input" v-model="course.is_featured"> <input type="checkbox" class="switch__input" v-model="course.is_featured">
<span class="switch__content" name="course-is-featured">Выделить</span> <span class="switch__content" name="course-is-featured">Выделить</span>
</label> </label>
<div v-if="!live" class="info__field field">
<div class="field__label field__label_gray">Теги</div>
<div class="field__wrap">
<!--<vue-tags :active="courseTags" :all="allTags"></vue-tags>-->
<select>
<options v-for="tag in tags" @click="selectTag(tag)">{{ tag.tag }}</options>
</select>
<input type="text" v-model="tagSearchQuery" v-class="{loading: tagsLoading}" />
<div>
<a v-for="tag in course.tags" class="tag">#{{ tag.tag }}</a>
</div>
</div>
</div>
</div> </div>
<div v-if="!live" class="info__fieldset"> <div v-if="!live" class="info__fieldset">
<div class="info__field field"> <div class="info__field field">
@ -238,20 +252,21 @@
</template> </template>
<script> <script>
import { ROLE_ADMIN, ROLE_AUTHOR } from './consts' import { ROLE_ADMIN } from './consts'
import LinkInput from './inputs/LinkInput' import LinkInput from './inputs/LinkInput'
import DatePicker from 'vuejs-datepicker' import DatePicker from 'vuejs-datepicker'
import BlockContent from './blocks/BlockContent' import BlockContent from './blocks/BlockContent'
import VueRedactor from './redactor/VueRedactor'; import VueRedactor from './redactor/VueRedactor';
import LilSelect from "./inputs/LilSelect"; import LilSelect from "./inputs/LilSelect";
import LessonRedactor from "./LessonRedactor"; import LessonRedactor from "./LessonRedactor";
import {api} from "../js/modules/api"; import { api } from "../js/modules/api";
import $ from 'jquery'; import $ from 'jquery';
import {required, minValue, numeric, url } from 'vuelidate/lib/validators' import { required, minValue, numeric, url } from 'vuelidate/lib/validators'
import slugify from 'slugify'; import slugify from 'slugify';
import Draggable from 'vuedraggable'; import Draggable from 'vuedraggable';
import {showNotification} from "../js/modules/notification"; import {showNotification} from "../js/modules/notification";
import createHistory from "history/createBrowserHistory"; import createHistory from "history/createBrowserHistory";
import { VueTags } from 'vue-tags-component';
import moment from 'moment' import moment from 'moment'
import _ from 'lodash' import _ from 'lodash'
@ -262,6 +277,10 @@
props: ["authorName", "authorPicture", "accessToken", "courseId", "live", "camp"], props: ["authorName", "authorPicture", "accessToken", "courseId", "live", "camp"],
data() { data() {
return { return {
tagsLoading: false,
tagSearchXhr: null,
tagSearchQuery: '',
courseTags: [{id: 1, name: 'tag'}, {id: 2, name: 'tag2'}],
disabledDates: { disabledDates: {
to: new Date(new Date().setDate(new Date().getDate() - 1)), to: new Date(new Date().setDate(new Date().getDate() - 1)),
}, },
@ -298,7 +317,8 @@
content: [], content: [],
gallery: { gallery: {
images: [], images: [],
} },
tags: [],
}, },
courseLoading: false, courseLoading: false,
courseSaving: false, courseSaving: false,
@ -453,6 +473,28 @@
} }
}, },
methods: { methods: {
searchTags(){
this.tagsLoading = true;
this.tagSearchXhr = api.get('tags', {tag: this.tagSearchQuery}).then(response => {
thistagsLoading = false;
this.tags = response.data;
});
},
selectTag(tag){
this.course.tags.push(tag);
},
addTag(){
this.tagsLoading = true;
this.api.post('tags', {tag: this.tagSearchQuery}).then(response => {
this.tagsLoading = false;
this.tagSearchQuery = '';
this.selectTag(response.data);
});
},
removeTag(tag){
const index = this.course.tags.findIndex(i => i.id == tag.id);
this.course.tags.splice(index, 1);
},
removeCover() { removeCover() {
if(! this.course.coverImageId){ if(! this.course.coverImageId){
return; return;
@ -1018,12 +1060,18 @@
}, },
deep: true, deep: true,
}, },
tagSearchQuery(){
if(this.tagSearchQuery){
this.searchTags();
}
}
}, },
components: { components: {
LessonRedactor, LessonRedactor,
LilSelect, LilSelect,
BlockContent, BlockContent,
VueTags,
'link-input': LinkInput, 'link-input': LinkInput,
'vue-datepicker': DatePicker, 'vue-datepicker': DatePicker,
'lesson-redactor': LessonRedactor, 'lesson-redactor': LessonRedactor,

@ -32,6 +32,7 @@ $green: #8ECFC0
$green-light: #5BD700 $green-light: #5BD700
$cyan: #B6DFD6 $cyan: #B6DFD6
$gray: #A7A7A7 $gray: #A7A7A7
$gray-dark: #888888
$blue: #4A90E2 $blue: #4A90E2
$viol: #B995D9 $viol: #B995D9
$viol2: #A186BD $viol2: #A186BD
@ -1110,8 +1111,8 @@ a[name]
color: $cl color: $cl
align-items: center align-items: center
&__counter &__counter
color: $gray-dark
margin-right: 5px margin-right: 5px
+fb
font-size: 13px font-size: 13px
+t +t
font-size: 12px font-size: 12px
@ -1151,7 +1152,12 @@ a[name]
&__info &__info
flex: 0 0 calc(100% - 50px) flex: 0 0 calc(100% - 50px)
display: flex display: flex
&__name, &__name
font-size: 13px
text-transform: capitalize
letter-spacing: 0.5px
line-height: 1.1
color: $gray-dark
&__date &__date
+fb +fb
font-size: 10px font-size: 10px
@ -1711,31 +1717,31 @@ a.grey-link
.courses .courses
&__list &__list
display: flex display: flex
margin: 0 -10px margin: 0 -20px
flex-wrap: wrap flex-wrap: wrap
+m +m
display: block margin: 0 -10px
margin: 0
&__item &__item
display: block display: block
margin: 0 10px 60px margin: 0 20px 75px
color: $cl color: $cl
flex: 0 0 calc(33.33% - 20px) flex: 0 0 288px
+t +t
margin-bottom: 50px !important margin-bottom: 50px !important
+m +m
margin: 0 0 30px margin: 0 5px 30px
flex: 0 0 calc(50% - 10px)
&__preview &__preview
display: block display: block
position: relative position: relative
margin-bottom: 15px margin-bottom: 15px
border-radius: 2px border-radius: 10px
color: $cl color: $cl
overflow: hidden overflow: hidden
+t +t
margin-bottom: 10px margin-bottom: 10px
&__preview:hover &__view &:hover
opacity: .9 box-shadow: 0 2px 14px 0 rgba(0, 0, 0, 0.15)
&__label &__label
position: absolute position: absolute
top: 0 top: 0
@ -1809,19 +1815,29 @@ a.grey-link
font-size: 12px font-size: 12px
letter-spacing: 2px letter-spacing: 2px
color: $cl color: $cl
+m
font-size: 10px
&__old-price &__old-price
margin-right: -15px margin-right: -15px
&__title &__title
display: block display: block
margin-bottom: 10px margin-bottom: 10px
+fs
font-size: 18px font-size: 18px
color: $cl color: $cl
+t +t
line-height: 1.33 line-height: 1.33
+m
font-size: 13px
&__content
color: $gray-dark
font-size: 14px
+m
font-size: 11px
&__theme &__theme
text-transform: uppercase text-transform: uppercase
flex: 1 flex: 1
+m
font-size: 10px
&__user &__user
margin-top: 20px margin-top: 20px
&_two &__item &_two &__item
@ -2131,22 +2147,21 @@ a.grey-link
&__head &__head
position: relative position: relative
height: 36px height: 36px
border-bottom: 1px solid rgba(82, 82, 82, 0.2) border: 1px solid rgba(82, 82, 82, 0.2)
transition: border-color .2s transition: border-color .2s
font-size: 18px font-size: 16px
line-height: 36px line-height: 36px
white-space: nowrap white-space: nowrap
text-overflow: ellipsis text-overflow: ellipsis
color: $gray
cursor: pointer cursor: pointer
transition: border-color .2s transition: border-color .2s
padding-right: 15px padding: 0px 18px
overflow: hidden overflow: hidden
&:after &:after
content: '' content: ''
position: absolute position: absolute
top: 14px top: 14px
right: 0 right: 18px
+arr(8,8,$cl,b) +arr(8,8,$cl,b)
&__drop &__drop
position: absolute position: absolute
@ -2469,12 +2484,12 @@ a.grey-link
.head .head
display: flex display: flex
margin-bottom: 50px margin-bottom: 30px
+t +t
margin-bottom: 40px margin-bottom: 40px
flex-wrap: wrap flex-wrap: wrap
&__right &__right
flex: 0 0 220px display: flex
+t +t
margin-left: auto !important margin-left: auto !important
+m +m
@ -2484,8 +2499,7 @@ a.grey-link
&__title &__title
position: relative position: relative
margin: 0 margin: 0
padding: 0 20px 0 260px flex: 1
flex: 0 0 calc(100% - 220px)
z-index: 4 z-index: 4
+t +t
max-width: 100% max-width: 100%
@ -2499,7 +2513,7 @@ a.grey-link
transform: translateY(-50%) transform: translateY(-50%)
z-index: -2 z-index: -2
&__field &__field
margin: 0 margin: 0 0 20px
&__text &__text
padding-right: 30px padding-right: 30px
font-size: 18px font-size: 18px
@ -4837,3 +4851,65 @@ a
padding-top: 80px padding-top: 80px
min-height: 200px min-height: 200px
padding-bottom: 60px padding-bottom: 60px
.course-search
text-align: center
&__form
position: relative
&__query
width: 600px
height: 50px
border-radius: 30px
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.1)
border: solid 1px rgba(0, 0, 0, 0.03)
background-color: #ffffff
padding: 10px 22px
font-size: 16px
+m
width: 100%
&__search
width: 58px
height: 40px
border-radius: 20px
top: 5px
position: absolute
margin-left: -65px
+m
margin-left: 0
right: 10px
& .icon
width: 17px
height: 17px
opacity: 0.5
margin-left: -4px
top: -2px
+m
margin-left: 0
&__text
font-size: 10px
color: #9b9b9b
margin: 30px 0 15px
&__tags
width: 460px
margin: 0 auto
.tag
height: 30px
border-radius: 20px
border: solid 1px #e7e7e7
padding: 5px 15px
display: inline-block
margin-right: 8px
.courses-filter
+m
display: none
&__select
width: 200px
margin-left: 20px

Loading…
Cancel
Save