diff --git a/programmer/static/programmer/js/infinite_scroll.js b/programmer/static/programmer/js/infinite_scroll.js new file mode 100644 index 0000000..50066b5 --- /dev/null +++ b/programmer/static/programmer/js/infinite_scroll.js @@ -0,0 +1,50 @@ +(function() { + let currentPage = window.currentPage || 1; + let totalPages = window.totalPages || 1; + let loading = false; + const container = document.getElementById('projects-container'); + const spinner = document.getElementById('loading-spinner'); + + if (!container || currentPage >= totalPages) return; + + function shouldLoad() { + if (loading) return false; + const scrollPosition = window.scrollY + window.innerHeight; + const threshold = document.documentElement.scrollHeight - 300; + return scrollPosition >= threshold; + } + + function loadNextPage() { + if (!shouldLoad()) return; + + loading = true; + if (spinner) spinner.style.display = 'block'; + + const nextPage = currentPage + 1; + fetch(`?page=${nextPage}`, { + headers: { 'X-Requested-With': 'XMLHttpRequest' } + }) + .then(response => response.json()) + .then(data => { + if (data.html) { + container.insertAdjacentHTML('beforeend', data.html); + currentPage = nextPage; + } + if (spinner) spinner.style.display = 'none'; + loading = false; + if (currentPage >= totalPages) spinner?.remove(); + }) + .catch(error => { + console.error('Ошибка загрузки:', error); + if (spinner) spinner.style.display = 'none'; + loading = false; + }); + } + + let timer; + window.addEventListener('scroll', () => { + clearTimeout(timer); + timer = setTimeout(loadNextPage, 150); + }); + window.addEventListener('load', loadNextPage); +})(); \ No newline at end of file diff --git a/programmer/templates/programmer/includes/project_card.html b/programmer/templates/programmer/includes/project_card.html new file mode 100644 index 0000000..3a57e94 --- /dev/null +++ b/programmer/templates/programmer/includes/project_card.html @@ -0,0 +1,56 @@ +
  • +
    +

    {{post.title}}

    + + + + +
    +
    +
    + 📋 Описание задачи + +
    +
    + {{post.description}} +
    +
    + +
    +
    + 🔧 Описание решения + +
    +
    + {{post.implementation}} +
    +
    + +
    +
    + ✅ Результат + +
    +
    + {{post.closing}} +
    +
    +
    + +
    +

    Опубликовано: {{post.time_create|date:"d.m.Y"}}

    +
    +
    +
  • \ No newline at end of file diff --git a/programmer/templates/programmer/includes/project_cards.html b/programmer/templates/programmer/includes/project_cards.html new file mode 100644 index 0000000..ac9b838 --- /dev/null +++ b/programmer/templates/programmer/includes/project_cards.html @@ -0,0 +1,5 @@ +{% autoescape off %} +{% for post in posts %} + {% include 'programmer/includes/project_card.html' %} +{% endfor %} +{% endautoescape %} \ No newline at end of file diff --git a/programmer/templates/programmer/solution.html b/programmer/templates/programmer/solution.html index babc513..be5ab33 100644 --- a/programmer/templates/programmer/solution.html +++ b/programmer/templates/programmer/solution.html @@ -13,75 +13,9 @@

    Реализованные решения и кейсы по автоматизации бизнес-процессов

    -
    - {% autoescape off %} - {% for p in posts %} -
  • -
    -

    {{p.title}}

    - - - - -
    -
    -
    - 📋 Описание задачи - -
    -
    - {{p.description}} -
    -
    - -
    -
    - 🔧 Описание решения - -
    -
    - {{p.implementation}} -
    -
    - -
    -
    - ✅ Результат - -
    -
    - {{p.closing}} -
    -
    -
    - -
    -

    Опубликовано: {{p.time_create|date:"d.m.Y"}}

    -
    -
    - - {% empty %} -
    -

    🚀 Проекты в разработке

    -

    Скоро здесь появятся новые кейсы автоматизации

    -
    - -
  • - {% endfor %} - {% endautoescape %} -
    + {% if not posts %}
    @@ -90,7 +24,23 @@
    {% endif %} - - + +{% if page_obj.has_next %} + +{% endif %} + + + + +{% endblock %} + +{% block extra_js %} + {% endblock %} \ No newline at end of file diff --git a/programmer/views.py b/programmer/views.py index 1cd0915..ca6f928 100644 --- a/programmer/views.py +++ b/programmer/views.py @@ -1,4 +1,4 @@ -from django.http import HttpResponse, HttpResponseNotFound, HttpRequest +from django.http import HttpResponse, HttpResponseNotFound, HttpRequest, JsonResponse from .models import * from django.shortcuts import render, redirect from django.contrib import messages @@ -14,6 +14,7 @@ from django.views.generic import TemplateView, ListView from .mixins import PageViewTrackingMixin, MenuContextMixin from .services import get_published_queryset, track_page_view from typing import Any, Dict, Type +from django.template.loader import render_to_string # menu = [ @@ -46,6 +47,27 @@ class BaseListView(PageViewTrackingMixin, MenuContextMixin, ListView): context.update(self.extra_context) return context + def render_to_response(self, context, **response_kwargs): + # Если это AJAX-запрос, возвращаем JSON с HTML фрагментом + if self.request.headers.get('x-requested-with') == 'XMLHttpRequest': + # Определяем шаблон для карточек + cards_template = getattr(self, 'cards_template', None) + if not cards_template: + # Генерируем имя по умолчанию: app/includes/model_cards.html + cards_template = f"{self.model._meta.app_label}/includes/{self.model._meta.model_name}_cards.html" + + html = render_to_string( + cards_template, + {self.context_object_name: context[self.context_object_name]}, + request=self.request + ) + return JsonResponse({ + 'html': html, + 'has_next': context['page_obj'].has_next() if context.get('page_obj') else False + }) + # Обычный запрос + return super().render_to_response(context, **response_kwargs) + class HomePageView(BasePageView): """Главная страница с формой обратной связи.""" @@ -101,6 +123,7 @@ class SolutionListView(BaseListView): template_name = 'programmer/solution.html' context_object_name = 'posts' paginate_by = 1 # Количество проектов на странице + cards_template = 'programmer/includes/project_cards.html' ordering = ['-time_create'] def get_queryset(self) -> QuerySet: diff --git a/static/programmer/js/infinite_scroll.js b/static/programmer/js/infinite_scroll.js new file mode 100644 index 0000000..50066b5 --- /dev/null +++ b/static/programmer/js/infinite_scroll.js @@ -0,0 +1,50 @@ +(function() { + let currentPage = window.currentPage || 1; + let totalPages = window.totalPages || 1; + let loading = false; + const container = document.getElementById('projects-container'); + const spinner = document.getElementById('loading-spinner'); + + if (!container || currentPage >= totalPages) return; + + function shouldLoad() { + if (loading) return false; + const scrollPosition = window.scrollY + window.innerHeight; + const threshold = document.documentElement.scrollHeight - 300; + return scrollPosition >= threshold; + } + + function loadNextPage() { + if (!shouldLoad()) return; + + loading = true; + if (spinner) spinner.style.display = 'block'; + + const nextPage = currentPage + 1; + fetch(`?page=${nextPage}`, { + headers: { 'X-Requested-With': 'XMLHttpRequest' } + }) + .then(response => response.json()) + .then(data => { + if (data.html) { + container.insertAdjacentHTML('beforeend', data.html); + currentPage = nextPage; + } + if (spinner) spinner.style.display = 'none'; + loading = false; + if (currentPage >= totalPages) spinner?.remove(); + }) + .catch(error => { + console.error('Ошибка загрузки:', error); + if (spinner) spinner.style.display = 'none'; + loading = false; + }); + } + + let timer; + window.addEventListener('scroll', () => { + clearTimeout(timer); + timer = setTimeout(loadNextPage, 150); + }); + window.addEventListener('load', loadNextPage); +})(); \ No newline at end of file