Рестайл общего шаблона и главной страницы

This commit is contained in:
2025-07-19 15:10:37 +08:00
parent 0b41503131
commit 1e188be6f3
2 changed files with 200 additions and 152 deletions

View File

@@ -10,103 +10,112 @@
{% block metakeywords %}самообновляемые,бесплатные,iptv-плейлисты,iptv,плейлисты{% endblock %} {% block metakeywords %}самообновляемые,бесплатные,iptv-плейлисты,iptv,плейлисты{% endblock %}
{% block head %}
<style>
.card {transition: box-shadow .2s, transform .2s}
.card.hover-success:hover {transform: translateY(-7px); box-shadow: rgba(var(--bs-success-rgb), 1) 0 5px 20px -5px}
.card.hover-danger:hover {transform: translateY(-7px); box-shadow: rgba(var(--bs-danger-rgb), 1) 0 5px 20px -5px}
.card.hover-secondary:hover {transform: translateY(-7px); box-shadow: rgba(var(--bs-secondary-rgb), 1) 0 5px 20px -5px}
</style>
<script>
function setDefaultLogo(imgtag) {
imgtag.onerror = null
imgtag.src = '/no-tvg-logo.png'
}
</script>
{% endblock %}
{% block header %} {% block header %}
<div class="row text-muted small"> <div class="d-flex flex-wrap justify-content-between align-items-center mb-4">
<div class="col-md"> <div class="mb-2">
Список изменён:&nbsp;{{ updatedAt }}&nbsp;МСК<br/> <h2 class="mb-0">Список плейлистов ({{ count }})</h2>
Плейлистов в списке:&nbsp;<strong>{{ count }}</strong> <div class="text-muted small">Изменён {{ updatedAt }} МСК</div>
</div> </div>
<div class="col-md"> <div class="d-flex flex-wrap gap-2 mb-2">
Состояние проверки:<br /> <span class="badge bg-success">online: {{ onlineCount }}</span>
<span class="me-1"> <span class="badge bg-danger">offline: {{ offlineCount }}</span>
<span class="badge me-1 bg-success text-dark">online</span>{{ onlineCount }} <span class="badge bg-secondary">unknown: {{ uncheckedCount }}</span>
</span>
<span class="me-1">
<span class="badge me-1 bg-danger text-dark">offline</span>{{ offlineCount }}
</span>
<span class="me-1">
<span class="badge me-1 bg-secondary text-dark" title="В очереди на проверку">unknown</span>{{ uncheckedCount }}
</span>
</div> </div>
</div> </div>
<hr/>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="table-responsive"> <div class="row g-4">
<table class="table table-responsive table-dark table-hover small">
<thead>
<tr>
<th class="col-1 text-center">Код</th>
<th class="col-8">Информация о плейлисте</th>
<th class="col-1 text-center">Каналов</th>
<th class="col-2 d-none d-sm-table-cell">Ссылка для ТВ</th>
</tr>
</thead>
<tbody>
{% for code, playlist in playlists %} {% for code, playlist in playlists %}
<tr class="pls" data-playlist-code="{{ code }}"> {% set statusClass = 'secondary' %}
<td class="text-center font-monospace code">{{ code }}</td>
<td class="info">
{% if playlist.isOnline is same as(true) %} {% if playlist.isOnline is same as(true) %}
<span class="badge small bg-success text-dark">online</span> {% set statusClass = 'success' %}
{% elseif playlist.isOnline is same as(false) %} {% elseif playlist.isOnline is same as(false) %}
<span class="badge small bg-danger text-dark">offline</span> {% set statusClass = 'danger' %}
{% elseif playlist.isOnline is same as(null) %}
<span class="badge small bg-secondary text-dark" title="В очереди на проверку">unknown</span>
{% endif %} {% endif %}
<div class="col-md-6 col-lg-4">
<div class="card bg-dark text-light h-100 border border-{{ statusClass }} hover-{{ statusClass }} position-relative">
<a href="/{{ code }}/details" class="text-decoration-none">
<div class="card-header d-flex align-items-center gap-2">
<span class="font-monospace text-{{ statusClass }}">{{ code }}</span>
<span class="badge bg-{{ statusClass }} ms-auto">
{% if playlist.isOnline is same as(true) %}online
{% elseif playlist.isOnline is same as(false) %}offline
{% elseif playlist.isOnline is same as(null) %}unknown
{% endif %}
</span>
{% if "adult" in playlist.tags %} {% if "adult" in playlist.tags %}
<span class="badge small bg-warning text-dark" title="Есть каналы для взрослых!">18+</span> <span class="badge bg-warning text-dark" title="Есть каналы для взрослых!">18+</span>
{% endif %} {% endif %}
<a href="/{{ code }}/details" class="text-light fw-bold text-decoration-none">{{ playlist.name }}</a> </div>
<div class="small mt-2"> </a>
<p class="my-1 d-none d-lg-block">
<div class="card-body position-relative z-2">
<a href="/{{ code }}/details" class="text-decoration-none">
<h5 class="card-title text-light">{{ playlist.name }}</h5>
</a>
<p class="card-text small text-secondary">{{ playlist.description }}</p>
<div class="d-flex flex-wrap gap-2 mb-1">
<span class="badge border border-secondary">
<ion-icon name="videocam-outline" class="me-1"></ion-icon>
{% if playlist.isOnline is not same as(null) %}
{{ playlist.channels|length }}&nbsp;каналов
{% endif %}
</span>
{% if playlist.groups|length > 0 %} {% if playlist.groups|length > 0 %}
<ion-icon name="folder-open-outline" title="Каналы разбиты на группы"></ion-icon> <span class="badge border border-secondary">
<ion-icon name="folder-open-outline" class="me-1"></ion-icon>&nbsp;{{ playlist.groups|length }}&nbsp;групп
</span>
{% endif %} {% endif %}
{% if playlist.hasTvg %} {% if playlist.hasTvg %}
<ion-icon name="newspaper-outline" title="Есть программа передач"></ion-icon> <span class="badge border border-secondary">
<ion-icon name="newspaper-outline" class="me-1"></ion-icon>&nbsp;ТВ-программа
</span>
{% endif %} {% endif %}
{% if playlist.hasCatchup %} {% if playlist.hasCatchup %}
<ion-icon name="play-back-outline" title="Есть перемотка (архив)"></ion-icon> <span class="badge border border-secondary">
<ion-icon name="play-back-outline" class="me-1"></ion-icon>&nbsp;Архив
</span>
{% endif %} {% endif %}
{{ playlist.description }}
</p>
{% if playlist.tags|length > 0 %}
<p class="my-1 d-none d-lg-block text-muted" title="Теги, присвоенные каналам при проверке">
<ion-icon name="pricetag-outline" class="me-1"></ion-icon>
{% for tag in playlist.tags %}
<span class="chtag">#{{ tag }}</span>
{% endfor %}
</p>
{% endif %}
<a href="/{{ code }}/details" class="text-light">Подробнее...</a>
</div> </div>
</td> </div>
<td class="text-center">
{% if (playlist.isOnline is not same as(null)) %} <div class="card-footer cursor-pointer"
{{ playlist.channels|length }} onclick="prompt('Скопируй адрес плейлиста. Если не работает, добавь \'.m3u\' в конец.', '{{ mirror_url(playlist.code) }}')"
{% else %} title="Нажми чтобы скопировать"
?
{% endif %}
</td>
<td class="d-none d-sm-table-cell">
<span onclick="prompt('Скопируй адрес плейлиста. Если не работает, добавь \'.m3u\' в конец.', '{{ mirror_url(playlist.code) }}')"
title="Нажми на ссылку, чтобы скопировать её в буфер обмена"
class="font-monospace cursor-pointer"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Нажми на ссылку, чтобы скопировать её в буфер обмена"
> >
<div class="d-flex justify-content-between align-items-center small">
<span class="font-monospace text-truncate">
{{ mirror_url(playlist.code) }} {{ mirror_url(playlist.code) }}
</span> </span>
</td> <ion-icon name="copy-outline"></ion-icon>
</tr> </div>
</div>
<a href="/{{ code }}/details" class="text-decoration-none">
</a>
</div>
</div>
{% endfor %} {% endfor %}
</tbody> </div>
</table>
{% if pageCount > 1 %} {% if pageCount > 1 %}
<div aria-label="pages"> <nav class="mt-4">
<ul class="pagination justify-content-center"> <ul class="pagination justify-content-center">
{% for page in range(1, pageCount) %} {% for page in range(1, pageCount) %}
{% if page == pageCurrent %} {% if page == pageCurrent %}
@@ -115,14 +124,13 @@
</li> </li>
{% else %} {% else %}
<li class="page-item"> <li class="page-item">
<a class="page-link bg-dark border-secondary text-light" href="page/{{ page }}">{{ page }}</a> <a class="page-link bg-dark text-light" href="page/{{ page }}">{{ page }}</a>
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</ul> </ul>
</div> </nav>
{% endif %} {% endif %}
</div>
{% endblock %} {% endblock %}
{% block footer %} {% block footer %}

View File

@@ -5,7 +5,7 @@
###########################################################################} ###########################################################################}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ru"> <html lang="ru" class="h-100">
<head> <head>
<title>{% block title %}{{ config('app.title') }}{% endblock %}</title> <title>{% block title %}{{ config('app.title') }}{% endblock %}</title>
<meta charset="utf-8"> <meta charset="utf-8">
@@ -30,38 +30,58 @@
<meta name="theme-color" content="#212529"> <meta name="theme-color" content="#212529">
{% block head %}{% endblock %} {% block head %}{% endblock %}
</head> </head>
<body class="bg-dark text-light"> <body class="d-flex flex-column h-100 bg-dark text-light">
<div class="container col-lg-10 mx-auto"> <header class="sticky-top bg-dark border-bottom border-secondary">
<header> <nav class="navbar navbar-expand-lg navbar-dark container px-2">
<nav class="navbar navbar-expand-lg navbar-dark"> <a class="navbar-brand d-flex align-items-center gap-2" href="/" title="На главную">
<a class="navbar-brand" href="/" title="На главную"> <img src="/favicon/favicon-32x32.png" alt="Логотип проекта" class="d-inline-block">
<img src="/favicon/favicon-32x32.png" class="d-inline-block px-lg-1" alt="Логотип проекта - emoji телевизора"/> <span>{{ config('app.title') }}</span>
{{ config('app.title') }}
</a> </a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
>
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav"> <ul class="navbar-nav ms-auto">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" target="_blank" href="/docs">Документация</a> <a class="nav-link" target="_blank" href="/docs">
<ion-icon name="document-text-outline" class="me-1"></ion-icon>&nbsp;Документация
</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" target="_blank" href="/docs/support.html">Помочь проекту</a> <a class="nav-link" target="_blank" href="/docs/support.html">
<ion-icon name="heart-outline" class="me-1"></ion-icon>&nbsp;Помочь проекту
</a>
</li> </li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" <a class="nav-link dropdown-toggle d-flex align-items-center"
href="#" href="#"
role="button" role="button"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-expanded="false" aria-expanded="false"
> >
Telegram <ion-icon name="paper-plane-outline" class="me-1"></ion-icon>&nbsp;Telegram
</a> </a>
<ul class="dropdown-menu dropdown-menu-dark"> <ul class="dropdown-menu dropdown-menu-dark">
<li><a class="dropdown-item" target="_blank" href="https://t.me/iptv_aggregator">Канал @iptv_aggregator</a></li> <li>
<li><a class="dropdown-item" target="_blank" href="https://t.me/iptv_aggregator_chat">Чат @iptv_aggregator_chat</a></li> <a class="dropdown-item d-flex align-items-center gap-2" target="_blank" href="https://t.me/iptv_aggregator">
<li><a class="dropdown-item" target="_blank" href="https://t.me/iptv_aggregator_bot">Бот @iptv_aggregator_bot</a></li> <ion-icon name="megaphone-outline"></ion-icon>&nbsp;Канал @iptv_aggregator
</a>
</li>
<li>
<a class="dropdown-item d-flex align-items-center gap-2" target="_blank" href="https://t.me/iptv_aggregator_chat">
<ion-icon name="chatbubbles-outline"></ion-icon>&nbsp;Чат @iptv_aggregator_chat
</a>
</li>
<li>
<a class="dropdown-item d-flex align-items-center gap-2" target="_blank" href="https://t.me/iptv_aggregator_bot">
<ion-icon name="chatbox-ellipses-outline"></ion-icon>&nbsp;Бот @iptv_aggregator_bot
</a>
</li>
</ul> </ul>
</li> </li>
</ul> </ul>
@@ -69,28 +89,48 @@
</nav> </nav>
</header> </header>
<section class="container h-100 pt-lg-3 px-0 pb-0"> <main class="flex-grow-1 container py-4">
{% block header %}{% endblock %} {% block header %}{% endblock %}
<div class="content-wrapper">
{% block content %}{% endblock %} {% block content %}{% endblock %}
</section> </div>
</main>
<footer class="py-4 text-center"> <footer class="bg-dark border-top border-secondary py-4">
<script src="/js/bootstrap.bundle.min.js"></script> <div class="container text-center">
{% block footer %}{% endblock %} <div class="d-flex flex-wrap justify-content-center gap-3 mb-3">
<a target="_blank" href="/docs">Документация</a>&nbsp;|&nbsp;<a <a target="_blank" href="/docs" class="text-light text-decoration-none d-flex align-items-center gap-1">
target="_blank" href="https://git.axenov.dev/IPTV">Исходники</a>&nbsp;|&nbsp;<a <ion-icon name="document-text-outline"></ion-icon>Документация
target="_blank" href="https://axenov.dev">axenov.dev</a>&nbsp;|&nbsp;<a </a>
target="_blank" href="https://t.me/iptv_aggregator">Канал</a>&nbsp;|&nbsp;<a <a target="_blank" href="https://git.axenov.dev/IPTV" class="text-light text-decoration-none d-flex align-items-center gap-1">
target="_blank" href="https://t.me/iptv_aggregator_chat">Чат</a>&nbsp;|&nbsp;<a <ion-icon name="code-slash-outline"></ion-icon>Исходники
target="_blank" href="https://t.me/iptv_aggregator_bot">Бот</a> </a>
<a target="_blank" href="https://axenov.dev" class="text-light text-decoration-none d-flex align-items-center gap-1">
<br> <ion-icon name="person-outline"></ion-icon>axenov.dev
<a class="small text-secondary" </a>
<a target="_blank" href="https://t.me/iptv_aggregator" class="text-light text-decoration-none d-flex align-items-center gap-1">
<ion-icon name="megaphone-outline"></ion-icon>Канал
</a>
<a target="_blank" href="https://t.me/iptv_aggregator_chat" class="text-light text-decoration-none d-flex align-items-center gap-1">
<ion-icon name="chatbubbles-outline"></ion-icon>Чат
</a>
<a target="_blank" href="https://t.me/iptv_aggregator_bot" class="text-light text-decoration-none d-flex align-items-center gap-1">
<ion-icon name="chatbox-ellipses-outline"></ion-icon>Бот
</a>
</div>
<div>
<a class="small text-secondary d-inline-flex align-items-center gap-1"
href="https://git.axenov.dev/IPTV/web/releases/tag/v{{ version() }}" href="https://git.axenov.dev/IPTV/web/releases/tag/v{{ version() }}"
target="_blank" target="_blank"
>v{{ version() }}</a> >
</footer> <ion-icon name="pricetag-outline"></ion-icon>v{{ version() }}
</a>
</div> </div>
</div>
</footer>
<script src="/js/bootstrap.bundle.min.js"></script>
{% block footer %}{% endblock %}
{% include("custom.twig") ignore missing %} {% include("custom.twig") ignore missing %}
</body> </body>