diff --git a/api/v1/views.py b/api/v1/views.py
index fdfb5a12..ffc240fd 100644
--- a/api/v1/views.py
+++ b/api/v1/views.py
@@ -11,6 +11,7 @@ from rest_framework.response import Response
from rest_framework.settings import api_settings
from django.utils.timezone import now
+from project.utils.db import format_sql, execute_sql
from . import ExtendedModelViewSet, BothListFormatMixin
from .serializers.config import ConfigSerializer
from .serializers.course import (
@@ -60,7 +61,7 @@ from apps.course.models import (
Comment, CourseComment, LessonComment,
Material, Lesson,
Like,
- LiveLessonComment, Tag)
+ LiveLessonComment, Tag, CourseTags)
from apps.config.models import Config
from apps.content.models import (
Banner, Image, Text, ImageText, Video,
@@ -784,3 +785,16 @@ class TagViewSet(ExtendedModelViewSet):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
+
+ @action(methods=['get'], detail=False, url_path='popular', authentication_classes=[], permission_classes=[])
+ def popular(self, *args, **kwargs):
+ sql = format_sql('''
+ select {ct.tag_id}
+ from {ct}
+ group by {ct.tag_id}
+ order by count(*) desc
+ limit 15''', ct=CourseTags)
+ tags = [t[0] for t in execute_sql(sql)]
+ tags = Tag.objects.filter(id__in=tags).order_by('tag')
+ serializer = self.get_serializer(tags, many=True)
+ return Response(serializer.data)
diff --git a/apps/course/templates/course/course_items.html b/apps/course/templates/course/course_items.html
index 26a3377f..7c56ad10 100644
--- a/apps/course/templates/course/course_items.html
+++ b/apps/course/templates/course/course_items.html
@@ -1,4 +1,29 @@
-{% for course in course_items %}
+{% for course in course_items %} {% load static %}
{% cycle '' 'theme_green' 'theme_violet' as theme_color silent %}
+ {% if forloop.counter == 6 and search_query and new_courses.count >= 4 %}
+
+
Новые курсы
+
+ {% for new_course in new_courses %}
+
+ {% endfor %}
+
+
+ {% endif %}
{% include "course/_items.html" %}
-{% endfor %}
\ No newline at end of file
+{% endfor %}
diff --git a/apps/course/views.py b/apps/course/views.py
index 5da37691..d4bd0298 100644
--- a/apps/course/views.py
+++ b/apps/course/views.py
@@ -340,6 +340,7 @@ class CoursesView(ListView):
context['search_query'] = self.request.GET.get('q', '')
context['banners'] = Banner.get_for_page(Banner.PAGE_COURSES)
context['course_items'] = Course.shuffle(context.get('course_items'))
+ context['new_courses'] = Course.objects.filter(status=Course.PUBLISHED).order_by('-created_at')[:4]
context['ages'] = Course.AGE_CHOICES[1:]
age = context.get('age')
if age and age[0]:
diff --git a/web/src/components/CourseRedactor.vue b/web/src/components/CourseRedactor.vue
index e529f12d..c4275eb3 100644
--- a/web/src/components/CourseRedactor.vue
+++ b/web/src/components/CourseRedactor.vue
@@ -130,9 +130,10 @@
+ :class="{loading: tagsLoading}" @keyup.enter="addTag" @keyup.down="selectFirstTag"
+ @click="tagsFocused = true"/>
@@ -289,6 +290,7 @@
props: ["authorName", "authorPicture", "accessToken", "courseId", "live", "camp"],
data() {
return {
+ tagsFocused: false,
tagsLoading: false,
tagSearchXhr: null,
tagSearchQuery: '',
@@ -486,10 +488,11 @@
},
methods: {
searchTags(){
- this.tagsLoading = true;
if(this.tagSearchTimeout){
clearTimeout(this.tagSearchTimeout);
}
+ this.tagsLoading = true;
+ this.tagsFocused = false;
this.tagSearchTimeout = setTimeout(() => {
api.getTags({search: this.tagSearchQuery}).then(response => {
this.tagsLoading = false;
@@ -505,6 +508,7 @@
$opts.focus();
},
selectTag(tag){
+ this.tagsFocused = false;
if(this.course.tags.findIndex(t => t.id == tag.id) > -1){
return;
}
@@ -961,6 +965,11 @@
let promises = [];
+ api.getPopularTags().then(response => {
+ const tags = this.course.tags.map(t => t.id);
+ this.tags = response.data.filter(t => tags.indexOf(t.id) == -1);
+ });
+
let cats = api.getCategories(this.accessToken);
promises.push(cats);
cats.then((response) => {
@@ -1052,7 +1061,7 @@
$(window).click(e => {
const cssClass = $(e.target).attr('class');
if(!cssClass || cssClass.indexOf('autocomplete') == -1){
- this.tags = [];
+ this.tagsFocused = false;
}
});
},
diff --git a/web/src/js/modules/api.js b/web/src/js/modules/api.js
index b0fd379c..73c35123 100644
--- a/web/src/js/modules/api.js
+++ b/web/src/js/modules/api.js
@@ -518,6 +518,9 @@ export const api = {
getTags: params => {
return api.get('/api/v1/tags/', {params});
},
+ getPopularTags: params => {
+ return api.get('/api/v1/tags/popular/', {params});
+ },
addTag: data => {
return api.post('/api/v1/tags/', data, {
headers: {
diff --git a/web/src/js/modules/courses.js b/web/src/js/modules/courses.js
index 20c16dba..ce3e4e42 100644
--- a/web/src/js/modules/courses.js
+++ b/web/src/js/modules/courses.js
@@ -40,7 +40,7 @@ $(document).ready(function () {
$('.course-search__search').click(function(e){
e.preventDefault();
page = 1;
- loadCourses();
+ loadCourses(true);
})
// Обработчик выбора категории
diff --git a/web/src/sass/_common.sass b/web/src/sass/_common.sass
index 5e65d4c2..0f9971f8 100755
--- a/web/src/sass/_common.sass
+++ b/web/src/sass/_common.sass
@@ -1860,6 +1860,42 @@ a.grey-link
padding-right: 15px
flex: 0 0 235px
+.new-courses
+ &__block-title
+ font-size: 25px
+ margin-bottom: 20px
+ margin-top: -6px
+ +m
+ margin-bottom: 10px
+
+ &__item
+ display: flex
+ margin-bottom: 10px
+
+ &__image
+ width: 80px
+ height: 57px
+ border-radius: 2px
+
+ &__details
+ margin-left: 15px
+
+ &__title
+ font-size: 15px
+ color: #333333
+ margin-bottom: 5px
+ display: block
+ +m
+ font-size: 14px
+
+ &__author
+ color: #888888
+ font-size: 10px
+ text-transform: uppercase
+ display: block
+ +m
+ font-size: 9px
+
.load
margin-top: 30px
+m