Добавил редактор статей, исправил комментарий, отредактировал отображение статей
This commit is contained in:
parent
2ba5793115
commit
0a2088e493
@ -89,6 +89,8 @@ INSTALLED_APPS = [
|
||||
'django.contrib.sites',
|
||||
'django.contrib.sitemaps',
|
||||
'blog.apps.BlogConfig',
|
||||
'taggit',
|
||||
'django_ckeditor_5',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
@ -221,4 +223,36 @@ SERVER_EMAIL = EMAIL_HOST_USER
|
||||
# ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'nikdizell@gmail.com').split(',')
|
||||
ADMIN_EMAILS = os.getenv('ADMIN_EMAILS').split(',')
|
||||
|
||||
# Путь для загрузки файлов редактором
|
||||
CKEDITOR_5_UPLOAD_FILE_TYPES = ['jpeg', 'jpg', 'png', 'gif', 'bmp', 'webp'] # допустимые типы
|
||||
CKEDITOR_5_UPLOAD_PATH = "uploads/ckeditor/" # папка внутри MEDIA_ROOT
|
||||
CKEDITOR_5_CONFIGS = {
|
||||
'default': {
|
||||
'toolbar': {
|
||||
'items': [
|
||||
'heading', '|',
|
||||
'bold', 'italic', 'underline', 'strikethrough', '|',
|
||||
'alignment',
|
||||
'link', 'bulletedList', 'numberedList', 'blockQuote', '|',
|
||||
'code', 'codeBlock', 'insertTable', '|',
|
||||
'imageUpload', 'mediaEmbed', '|',
|
||||
'undo', 'redo',
|
||||
'fontSize', 'fontFamily', 'highlight', 'horizontalLine', 'removeFormat', '|',
|
||||
]
|
||||
},
|
||||
'image': {
|
||||
'toolbar': ['imageTextAlternative', '|', 'imageStyle:alignLeft', 'imageStyle:alignCenter', 'imageStyle:alignRight'],
|
||||
'styles': ['alignLeft', 'alignCenter', 'alignRight'],
|
||||
},
|
||||
'table': {
|
||||
'contentToolbar': ['tableColumn', 'tableRow', 'mergeTableCells'],
|
||||
},
|
||||
'mediaEmbed': {
|
||||
'previewsInData': True, # для встраивания видео (YouTube и др.)
|
||||
},
|
||||
'height': 500,
|
||||
'width': '100%',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -16,8 +16,6 @@ Including another URLconf
|
||||
"""
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
|
||||
from OneCprogsite import settings
|
||||
from programmer.views import *
|
||||
from django.urls import path, include
|
||||
@ -26,6 +24,7 @@ urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('', include('programmer.urls')),
|
||||
path('blog/', include('blog.urls')),
|
||||
path('ckeditor5/', include('django_ckeditor_5.urls')),
|
||||
# path('', index, name='home'),
|
||||
# path('about/', about, name='about'),
|
||||
# path('solution/', solution, name='solution'),
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
from django.contrib import admin
|
||||
from .models import Category, Article, Comment
|
||||
from django.db import models
|
||||
from django_ckeditor_5.widgets import CKEditor5Widget
|
||||
from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
@admin.register(Category)
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
@ -9,14 +14,47 @@ class CategoryAdmin(admin.ModelAdmin):
|
||||
|
||||
@admin.register(Article)
|
||||
class ArticleAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'category', 'author', 'is_published', 'views_count', 'time_create')
|
||||
list_filter = ('is_published', 'category', 'time_create')
|
||||
search_fields = ('title', 'content')
|
||||
formfield_overrides = {
|
||||
models.TextField: {'widget': CKEditor5Widget(config_name='default')},
|
||||
}
|
||||
list_display = ('title', 'category', 'author', 'is_published', 'views_count', 'time_create', 'tag_list')
|
||||
list_filter = ('is_published', 'category', 'tags', 'time_create')
|
||||
search_fields = ('title', 'content', 'tags__name')
|
||||
prepopulated_fields = {'slug': ('title',)}
|
||||
autocomplete_fields = ('author',) # вместо raw_id_fields для удобства
|
||||
# filter_horizontal = ('tags',) # для удобного выбора тегов
|
||||
raw_id_fields = ('author',) # удобно при большом количестве пользователей
|
||||
date_hierarchy = 'time_create'
|
||||
actions = ['make_published', 'make_unpublished']
|
||||
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': ('title', 'slug', 'category', 'author', 'tags', 'is_published')
|
||||
}),
|
||||
('Содержание', {
|
||||
'fields': ('content', 'image'),
|
||||
'description': 'Основной текст статьи. Используйте редактор для форматирования.'
|
||||
}),
|
||||
('SEO', {
|
||||
'fields': ('meta_description',),
|
||||
'classes': ('collapse',),
|
||||
}),
|
||||
('Служебное', {
|
||||
'fields': ('views_count',),
|
||||
'classes': ('collapse',),
|
||||
}),
|
||||
)
|
||||
|
||||
readonly_fields = ('views_count',) # только для просмотра
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super().get_queryset(request).prefetch_related('tags')
|
||||
|
||||
def tag_list(self, obj):
|
||||
return ", ".join(o.name for o in obj.tags.all())
|
||||
|
||||
tag_list.short_description = "Теги"
|
||||
|
||||
def make_published(self, request, queryset):
|
||||
queryset.update(is_published=True)
|
||||
make_published.short_description = "Опубликовать выбранные статьи"
|
||||
@ -25,6 +63,17 @@ class ArticleAdmin(admin.ModelAdmin):
|
||||
queryset.update(is_published=False)
|
||||
make_unpublished.short_description = "Снять с публикации"
|
||||
|
||||
# Добавляем кнопку предпросмотра для черновиков
|
||||
def view_on_site(self, obj):
|
||||
if obj.is_published:
|
||||
return obj.get_absolute_url()
|
||||
else:
|
||||
# ссылка на предпросмотр черновика
|
||||
return reverse('blog:draft_preview', args=[obj.slug])
|
||||
|
||||
view_on_site.short_description = "Предпросмотр"
|
||||
|
||||
|
||||
@admin.register(Comment)
|
||||
class CommentAdmin(admin.ModelAdmin):
|
||||
list_display = ('author_name', 'article', 'is_moderated', 'time_create')
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
from django import forms
|
||||
from .models import Comment
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
|
||||
class CommentForm(forms.ModelForm):
|
||||
class Meta:
|
||||
@ -8,6 +11,17 @@ class CommentForm(forms.ModelForm):
|
||||
widgets = {
|
||||
'author_name': forms.TextInput(attrs={'class': 'form-input', 'placeholder': 'Ваше имя'}),
|
||||
'author_email': forms.EmailInput(attrs={'class': 'form-input', 'placeholder': 'Ваш email'}),
|
||||
'content': forms.Textarea(attrs={'class': 'form-textarea', 'rows': 4, 'placeholder': 'Текст комментария'}),
|
||||
'content': forms.Textarea(attrs={'class': 'form-textarea', 'rows': 4, 'placeholder': 'Текст комментария',
|
||||
'autocomplete': 'off'}),
|
||||
}
|
||||
|
||||
def clean_content(self):
|
||||
content = self.cleaned_data['content']
|
||||
# Удаляем HTML-теги и проверяем, остался ли текст
|
||||
clean_text = strip_tags(content)
|
||||
if not clean_text.strip():
|
||||
raise ValidationError("Комментарий не должен состоять только из HTML-тегов")
|
||||
# Если хотите запретить любые теги, можно сравнить content и clean_text
|
||||
if content != clean_text:
|
||||
raise ValidationError("HTML-теги в комментариях запрещены")
|
||||
return content
|
||||
|
||||
@ -2,6 +2,7 @@ from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth import get_user_model
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@ -20,6 +21,7 @@ class Category(models.Model):
|
||||
|
||||
class Article(models.Model):
|
||||
"""Статья блога"""
|
||||
tags = TaggableManager(blank=True, verbose_name="Теги")
|
||||
title = models.CharField(max_length=200, verbose_name="Заголовок")
|
||||
slug = models.SlugField(max_length=220, unique=True, verbose_name="URL")
|
||||
category = models.ForeignKey(
|
||||
@ -38,6 +40,12 @@ class Article(models.Model):
|
||||
is_published = models.BooleanField(default=True, verbose_name="Опубликовано")
|
||||
time_create = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
|
||||
time_update = models.DateTimeField(auto_now=True, verbose_name="Дата изменения")
|
||||
meta_description = models.CharField(
|
||||
max_length=160,
|
||||
blank=True,
|
||||
verbose_name="Meta-описание",
|
||||
help_text="Краткое описание для поисковых систем (до 160 символов)"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Статья"
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
{% load django_bootstrap5 %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{% static 'programmer/css/recall.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'programmer/css/recall.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@ -21,13 +21,13 @@
|
||||
<img src="{{ article.image.url }}" alt="{{ article.title }}" style="max-width: 100%; margin-bottom: 1rem;">
|
||||
{% endif %}
|
||||
<div class="card-content">
|
||||
{{ article.content|linebreaks }}
|
||||
{{ article.content|safe }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Комментарии -->
|
||||
<h3>Комментарии</h3>
|
||||
{% for comment in article.comments.all %}
|
||||
{% for comment in moderated_comments %}
|
||||
<div class="modern-card" style="margin-bottom: 1rem;">
|
||||
<p><strong>{{ comment.author_name }}</strong> ({{ comment.time_create|date:"d.m.Y H:i" }})</p>
|
||||
<p>{{ comment.content|linebreaks }}</p>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{% extends 'programmer/base.html' %}
|
||||
{% load static %}
|
||||
{% load blog_extras %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{% static 'programmer/css/recall.css' %}">
|
||||
@ -23,7 +24,7 @@
|
||||
{% for article in articles %}
|
||||
<div class="modern-card">
|
||||
<h2><a href="{{ article.get_absolute_url }}">{{ article.title }}</a></h2>
|
||||
<p>{{ article.content|truncatewords:30 }}</p>
|
||||
<p>{{ article.content|clean_html|truncatewords:30 }}</p>
|
||||
<div class="meta">
|
||||
<span>📅 {{ article.time_create|date:"d.m.Y" }}</span>
|
||||
<span>👁 {{ article.views_count }}</span>
|
||||
|
||||
0
blog/templatetags/__init__.py
Normal file
0
blog/templatetags/__init__.py
Normal file
14
blog/templatetags/blog_extras.py
Normal file
14
blog/templatetags/blog_extras.py
Normal file
@ -0,0 +1,14 @@
|
||||
from django import template
|
||||
from django.utils.html import strip_tags
|
||||
import html
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def clean_html(value):
|
||||
"""Удаляет HTML-теги и заменяет сущности на символы."""
|
||||
# Декодируем HTML-сущности (например, → пробел)
|
||||
value = html.unescape(value)
|
||||
# Удаляем все HTML-теги
|
||||
return strip_tags(value)
|
||||
@ -7,5 +7,6 @@ urlpatterns = [
|
||||
path('', views.ArticleListView.as_view(), name='article_list'),
|
||||
path('category/<slug:category_slug>/', views.ArticleListView.as_view(), name='category_detail'),
|
||||
path('<slug:slug>/', views.ArticleDetailView.as_view(), name='article_detail'),
|
||||
path('draft/<slug:slug>/', views.ArticleDraftPreviewView.as_view(), name='draft_preview'),
|
||||
]
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ from .services import (
|
||||
from .forms import CommentForm
|
||||
from django.http import Http404
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
|
||||
|
||||
class ArticleListView(MenuContextMixin, ListView):
|
||||
@ -66,6 +67,12 @@ class ArticleDetailView(MenuContextMixin, DetailView, CreateView):
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
def get_initial(self):
|
||||
"""Возвращает пустые начальные данные для формы комментария."""
|
||||
initial = super().get_initial()
|
||||
initial['content'] = ''
|
||||
return initial
|
||||
|
||||
def form_valid(self, form):
|
||||
comment = add_comment_to_article(
|
||||
article=self.object,
|
||||
@ -74,4 +81,19 @@ class ArticleDetailView(MenuContextMixin, DetailView, CreateView):
|
||||
messages.success(self.request, "Ваш комментарий отправлен на модерацию.")
|
||||
return redirect(self.object.get_absolute_url())
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
# Добавляем только одобренные комментарии
|
||||
context['moderated_comments'] = self.object.comments.filter(is_moderated=True)
|
||||
return context
|
||||
|
||||
|
||||
@method_decorator(staff_member_required, name='dispatch')
|
||||
class ArticleDraftPreviewView(DetailView):
|
||||
model = Article
|
||||
template_name = 'blog/article_detail.html' # используем тот же шаблон
|
||||
context_object_name = 'article'
|
||||
|
||||
def get_queryset(self):
|
||||
# Для предпросмотра показываем даже неопубликованные статьи
|
||||
return Article.objects.all() # без фильтрации по is_published
|
||||
@ -21,7 +21,7 @@ class MenuContextMixin(ContextMixin):
|
||||
menu = [
|
||||
{'title': "Главная", 'url_name': 'home'},
|
||||
{'title': "Проекты", 'url_name': 'solution'},
|
||||
# {'title': "Статьи", 'url_name': 'blog'},
|
||||
{'title': "Статьи", 'url_name': 'blog'},
|
||||
{'title': "Отзывы", 'url_name': 'recall'},
|
||||
{'title': "Обо мне", 'url_name': 'about'}
|
||||
]
|
||||
|
||||
139
programmer/static/blog/css/article_content.css
Normal file
139
programmer/static/blog/css/article_content.css
Normal file
@ -0,0 +1,139 @@
|
||||
/* Основная типографика */
|
||||
.article-content {
|
||||
font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 800px; /* при необходимости */
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.article-content h1,
|
||||
.article-content h2,
|
||||
.article-content h3,
|
||||
.article-content h4,
|
||||
.article-content h5,
|
||||
.article-content h6 {
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.article-content p {
|
||||
margin-bottom: 1.2em;
|
||||
}
|
||||
|
||||
.article-content blockquote {
|
||||
border-left: 4px solid #ddd;
|
||||
padding-left: 1em;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.article-content pre {
|
||||
background: #f5f5f5;
|
||||
padding: 1em;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.article-content code {
|
||||
background: #f0f0f0;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Таблицы */
|
||||
.article-content table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.article-content th,
|
||||
.article-content td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.article-content th {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
/* Изображения */
|
||||
.article-content figure.image {
|
||||
display: inline-block;
|
||||
margin: 1em 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.article-content figure.image img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Выравнивание изображений (классы, используемые CKEditor 5) */
|
||||
.article-content .image-style-align-left {
|
||||
float: left;
|
||||
margin-right: 1.5em;
|
||||
margin-bottom: 1em;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.article-content .image-style-align-right {
|
||||
float: right;
|
||||
margin-left: 1.5em;
|
||||
margin-bottom: 1em;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.article-content .image-style-align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Подписи к изображениям (если используются) */
|
||||
.article-content figure.image figcaption {
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
margin-top: 0.3em;
|
||||
}
|
||||
|
||||
/* Встроенные медиа (видео) */
|
||||
.article-content .media {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-bottom: 56.25%; /* 16:9 */
|
||||
height: 0;
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
|
||||
.article-content .media iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Списки */
|
||||
.article-content ul,
|
||||
.article-content ol {
|
||||
padding-left: 2em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
/* Горизонтальная линия */
|
||||
.article-content hr {
|
||||
border: none;
|
||||
border-top: 2px solid #eee;
|
||||
margin: 2em 0;
|
||||
}
|
||||
@ -160,6 +160,14 @@
|
||||
|
||||
/* Адаптивность для мобильных устройств */
|
||||
@media (max-width: 768px) {
|
||||
.content-card .image-style-align-left,
|
||||
.content-card .image-style-align-right {
|
||||
float: none !important;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.recall-content {
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
@ -226,3 +234,21 @@
|
||||
background: linear-gradient(transparent, rgba(0,0,0,0.7));
|
||||
}
|
||||
}
|
||||
|
||||
.content-card img {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.content-card figure.image {
|
||||
max-width: 100%;
|
||||
margin: 1rem auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content-card figure.image img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
@ -867,7 +867,25 @@ body {
|
||||
border: 1px solid var(--border-light);
|
||||
margin-bottom: 2rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
/* overflow: visible !important; */
|
||||
}
|
||||
|
||||
.content-card img {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.content-card figure.image {
|
||||
max-width: 100%;
|
||||
margin: 1rem auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content-card figure.image img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.content-card::before {
|
||||
@ -1298,6 +1316,14 @@ body {
|
||||
.breadcrumbs {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.content-card .image-style-align-left,
|
||||
.content-card .image-style-align-right {
|
||||
float: none !important;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
|
||||
139
static/blog/css/article_content.css
Normal file
139
static/blog/css/article_content.css
Normal file
@ -0,0 +1,139 @@
|
||||
/* Основная типографика */
|
||||
.article-content {
|
||||
font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 800px; /* при необходимости */
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.article-content h1,
|
||||
.article-content h2,
|
||||
.article-content h3,
|
||||
.article-content h4,
|
||||
.article-content h5,
|
||||
.article-content h6 {
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.article-content p {
|
||||
margin-bottom: 1.2em;
|
||||
}
|
||||
|
||||
.article-content blockquote {
|
||||
border-left: 4px solid #ddd;
|
||||
padding-left: 1em;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.article-content pre {
|
||||
background: #f5f5f5;
|
||||
padding: 1em;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.article-content code {
|
||||
background: #f0f0f0;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Таблицы */
|
||||
.article-content table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.article-content th,
|
||||
.article-content td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.article-content th {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
/* Изображения */
|
||||
.article-content figure.image {
|
||||
display: inline-block;
|
||||
margin: 1em 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.article-content figure.image img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Выравнивание изображений (классы, используемые CKEditor 5) */
|
||||
.article-content .image-style-align-left {
|
||||
float: left;
|
||||
margin-right: 1.5em;
|
||||
margin-bottom: 1em;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.article-content .image-style-align-right {
|
||||
float: right;
|
||||
margin-left: 1.5em;
|
||||
margin-bottom: 1em;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.article-content .image-style-align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Подписи к изображениям (если используются) */
|
||||
.article-content figure.image figcaption {
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
margin-top: 0.3em;
|
||||
}
|
||||
|
||||
/* Встроенные медиа (видео) */
|
||||
.article-content .media {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-bottom: 56.25%; /* 16:9 */
|
||||
height: 0;
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
|
||||
.article-content .media iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Списки */
|
||||
.article-content ul,
|
||||
.article-content ol {
|
||||
padding-left: 2em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
/* Горизонтальная линия */
|
||||
.article-content hr {
|
||||
border: none;
|
||||
border-top: 2px solid #eee;
|
||||
margin: 2em 0;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user