mirror of
https://github.com/EDeev/deev.space.git
synced 2026-06-15 11:01:10 +03:00
v. 1.3
This commit is contained in:
parent
9980762ae6
commit
5f2ebd8792
7 changed files with 525 additions and 264 deletions
|
|
@ -1,156 +1,177 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', '*****')
|
||||
|
||||
DEBUG = os.environ.get('DJANGO_DEBUG', 'False') == 'True'
|
||||
|
||||
ALLOWED_HOSTS = ['deev.space', 'deev.su', 'www.deev.space', 'www.deev.su', 'localhost', '127.0.0.1']
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sitemaps',
|
||||
'main.apps.MainConfig',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'dspace.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [BASE_DIR / 'templates'],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'main.context_processors.site_settings',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'dspace.wsgi.application'
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
AUTH_USER_MODEL = 'main.CustomUser'
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = []
|
||||
|
||||
LANGUAGE_CODE = 'ru-ru'
|
||||
TIME_ZONE = 'Europe/Moscow'
|
||||
USE_I18N = True
|
||||
USE_TZ = True
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATICFILES_DIRS = [BASE_DIR / 'static']
|
||||
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = BASE_DIR / 'media'
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
# Yandex SmartCaptcha Settings
|
||||
SMARTCAPTCHA_CLIENT_KEY = os.environ.get('SMARTCAPTCHA_CLIENT_KEY', '*****')
|
||||
SMARTCAPTCHA_SERVER_KEY = os.environ.get('SMARTCAPTCHA_SERVER_KEY', '*****')
|
||||
|
||||
# Telegram Bot Settings
|
||||
TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN', '')
|
||||
TELEGRAM_CHANNEL_ID = os.environ.get('TELEGRAM_CHANNEL_ID', '')
|
||||
|
||||
# VK API Settings
|
||||
VK_ACCESS_TOKEN = os.environ.get('VK_ACCESS_TOKEN', '')
|
||||
VK_GROUP_ID = os.environ.get('VK_GROUP_ID', '')
|
||||
|
||||
# Email Settings
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
EMAIL_HOST = os.environ.get('EMAIL_HOST', 'smtp.yandex.ru')
|
||||
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 465))
|
||||
EMAIL_USE_SSL = True
|
||||
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '')
|
||||
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '')
|
||||
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'egor@deev.space')
|
||||
CONTACT_EMAIL = os.environ.get('CONTACT_EMAIL', 'egor@deev.space')
|
||||
|
||||
# Site Settings
|
||||
SITE_NAME = 'deev.space'
|
||||
SITE_AUTHOR = 'Деев Егор Викторович'
|
||||
SITE_DESCRIPTION = 'Персональный сайт backend-разработчика'
|
||||
|
||||
# Session Settings
|
||||
SESSION_COOKIE_AGE = 31536000 # 1 year
|
||||
SESSION_COOKIE_SECURE = not DEBUG
|
||||
CSRF_COOKIE_SECURE = not DEBUG
|
||||
|
||||
# Security Settings (Production)
|
||||
if not DEBUG:
|
||||
SECURE_BROWSER_XSS_FILTER = True
|
||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||
X_FRAME_OPTIONS = 'DENY'
|
||||
SECURE_SSL_REDIRECT = True
|
||||
SECURE_HSTS_SECONDS = 31536000
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
||||
SECURE_HSTS_PRELOAD = True
|
||||
|
||||
# Logging
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': '{levelname} {asctime} {module} {message}',
|
||||
'style': '{',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'file': {
|
||||
'level': 'ERROR',
|
||||
'class': 'logging.FileHandler',
|
||||
'filename': BASE_DIR / 'logs' / 'django.log',
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django': {
|
||||
'handlers': ['console'] if DEBUG else ['file', 'console'],
|
||||
'level': 'INFO',
|
||||
},
|
||||
'main': {
|
||||
'handlers': ['console'] if DEBUG else ['file', 'console'],
|
||||
'level': 'DEBUG' if DEBUG else 'INFO',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# Create logs directory
|
||||
import os
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', '*****')
|
||||
|
||||
DEBUG = os.environ.get('DJANGO_DEBUG', 'False') == 'True'
|
||||
|
||||
ALLOWED_HOSTS = ['deev.space', 'deev.su', 'www.deev.space', 'www.deev.su', 'localhost', '127.0.0.1']
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sitemaps',
|
||||
'main.apps.MainConfig',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'dspace.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [BASE_DIR / 'templates'],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'main.context_processors.site_settings',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'dspace.wsgi.application'
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
AUTH_USER_MODEL = 'main.CustomUser'
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
LANGUAGE_CODE = 'ru-ru'
|
||||
TIME_ZONE = 'Europe/Moscow'
|
||||
USE_I18N = True
|
||||
USE_TZ = True
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATICFILES_DIRS = [BASE_DIR / 'static']
|
||||
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||
|
||||
# Хеширование статических файлов в production
|
||||
if not DEBUG:
|
||||
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
||||
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = BASE_DIR / 'media'
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
# Yandex SmartCaptcha Settings
|
||||
SMARTCAPTCHA_CLIENT_KEY = os.environ.get('SMARTCAPTCHA_CLIENT_KEY', '*****')
|
||||
SMARTCAPTCHA_SERVER_KEY = os.environ.get('SMARTCAPTCHA_SERVER_KEY', '*****')
|
||||
|
||||
# Telegram Bot Settings
|
||||
TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN', '')
|
||||
TELEGRAM_CHANNEL_ID = os.environ.get('TELEGRAM_CHANNEL_ID', '')
|
||||
|
||||
# VK API Settings
|
||||
VK_ACCESS_TOKEN = os.environ.get('VK_ACCESS_TOKEN', '')
|
||||
VK_GROUP_ID = os.environ.get('VK_GROUP_ID', '')
|
||||
|
||||
# Email Settings
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
EMAIL_HOST = os.environ.get('EMAIL_HOST', 'smtp.yandex.ru')
|
||||
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 465))
|
||||
EMAIL_USE_SSL = True
|
||||
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '')
|
||||
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '')
|
||||
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'egor@deev.space')
|
||||
CONTACT_EMAIL = os.environ.get('CONTACT_EMAIL', 'egor@deev.space')
|
||||
|
||||
# Site Settings
|
||||
SITE_NAME = 'deev.space'
|
||||
SITE_AUTHOR = 'Деев Егор Викторович'
|
||||
SITE_DESCRIPTION = 'Персональный сайт backend-разработчика'
|
||||
|
||||
# Session Settings
|
||||
SESSION_COOKIE_AGE = 31536000 # 1 year
|
||||
SESSION_COOKIE_SECURE = not DEBUG
|
||||
CSRF_COOKIE_SECURE = not DEBUG
|
||||
|
||||
# Security Settings (Production)
|
||||
if not DEBUG:
|
||||
SECURE_BROWSER_XSS_FILTER = True
|
||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||
X_FRAME_OPTIONS = 'DENY'
|
||||
SECURE_SSL_REDIRECT = True
|
||||
SECURE_HSTS_SECONDS = 31536000
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
||||
SECURE_HSTS_PRELOAD = True
|
||||
|
||||
# Logging
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': '{levelname} {asctime} {module} {message}',
|
||||
'style': '{',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'file': {
|
||||
'level': 'ERROR',
|
||||
'class': 'logging.FileHandler',
|
||||
'filename': BASE_DIR / 'logs' / 'django.log',
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django': {
|
||||
'handlers': ['console'] if DEBUG else ['file', 'console'],
|
||||
'level': 'INFO',
|
||||
},
|
||||
'main': {
|
||||
'handlers': ['console'] if DEBUG else ['file', 'console'],
|
||||
'level': 'DEBUG' if DEBUG else 'INFO',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# Create logs directory
|
||||
(BASE_DIR / 'logs').mkdir(exist_ok=True)
|
||||
|
|
@ -399,46 +399,273 @@
|
|||
}
|
||||
|
||||
.article-body {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.8;
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.7;
|
||||
color: var(--text-secondary);
|
||||
text-align: justify;
|
||||
text-justify: inter-word;
|
||||
hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
}
|
||||
|
||||
.article-body p {
|
||||
margin-bottom: var(--space-lg);
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 0;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.article-body h2,
|
||||
.article-body h3,
|
||||
.article-body h4,
|
||||
.article-body h5,
|
||||
.article-body h6 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.article-body h2 {
|
||||
color: var(--text-primary);
|
||||
margin-top: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
.article-body h3 {
|
||||
color: var(--text-primary);
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
|
||||
.article-body h4 {
|
||||
color: var(--text-primary);
|
||||
margin-top: var(--space-2xl);
|
||||
margin-bottom: var(--space-md);
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
.article-body ul,
|
||||
.article-body ol {
|
||||
margin-bottom: var(--space-lg);
|
||||
padding-left: var(--space-xl);
|
||||
margin-bottom: 1.25rem;
|
||||
margin-top: 0.75rem;
|
||||
padding-left: 2rem;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.article-body li {
|
||||
margin-bottom: var(--space-sm);
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.6;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.article-body li p {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.article-body img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--radius-md);
|
||||
margin: var(--space-lg) 0;
|
||||
margin: 1.5rem 0;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.article-body blockquote {
|
||||
border-left: 4px solid var(--primary-color);
|
||||
padding-left: var(--space-lg);
|
||||
margin: var(--space-lg) 0;
|
||||
font-style: italic;
|
||||
padding: 1rem 1.5rem;
|
||||
margin: 1.5rem 0;
|
||||
background: var(--bg-card);
|
||||
border-radius: var(--radius-sm);
|
||||
font-style: normal;
|
||||
color: var(--text-secondary);
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.article-body blockquote p {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.article-body blockquote p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.article-body hr {
|
||||
border: none;
|
||||
height: 1px;
|
||||
background: var(--border-color);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.article-body table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1.5rem 0;
|
||||
background: var(--bg-card);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.article-body th,
|
||||
.article-body td {
|
||||
padding: 0.75rem 1rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.article-body th {
|
||||
background: var(--bg-elevated);
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.article-body tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.article-body tr:hover {
|
||||
background: var(--bg-elevated);
|
||||
}
|
||||
|
||||
.article-body code {
|
||||
font-family: 'Fira Code', 'Monaco', 'Consolas', monospace;
|
||||
background: var(--bg-elevated);
|
||||
padding: 0.2em 0.5em;
|
||||
border-radius: var(--radius-xs);
|
||||
font-size: 0.9em;
|
||||
color: var(--primary-light);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.article-body pre {
|
||||
position: relative;
|
||||
background: var(--bg-darker);
|
||||
padding: 1.25rem;
|
||||
border-radius: var(--radius-md);
|
||||
overflow-x: auto;
|
||||
margin: 1.5rem 0;
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.article-body pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
border: none;
|
||||
color: #e0e0e0;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.article-body pre:hover .code-copy-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.code-copy-btn {
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
right: 0.75rem;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.4rem 0.75rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.code-copy-btn:hover {
|
||||
background: var(--primary-muted);
|
||||
border-color: var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.code-copy-btn.copied {
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
border-color: var(--success-color);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.code-copy-btn i {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.code-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--bg-elevated);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md) var(--radius-md) 0 0;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.code-language {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.article-body pre.has-header {
|
||||
border-radius: 0 0 var(--radius-md) var(--radius-md);
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.article-body h2,
|
||||
.article-body h3 {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-anchor {
|
||||
position: absolute;
|
||||
left: -1.5rem;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.article-body h2:hover .header-anchor,
|
||||
.article-body h3:hover .header-anchor {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.header-anchor:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.article-body pre,
|
||||
.article-body pre code {
|
||||
text-align: left;
|
||||
hyphens: none;
|
||||
-webkit-hyphens: none;
|
||||
}
|
||||
|
||||
.article-body table,
|
||||
.article-body th,
|
||||
.article-body td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.article-body figcaption,
|
||||
.carousel-caption,
|
||||
.collage-caption {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Article Footer */
|
||||
|
|
@ -689,7 +916,7 @@
|
|||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.comment {
|
||||
div.comment {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-xl);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
initBackToTop();
|
||||
initMessages();
|
||||
initContactForm();
|
||||
initCodeBlocks();
|
||||
addHeaderAnchors();
|
||||
});
|
||||
|
||||
// ===== AOS Animation =====
|
||||
|
|
@ -453,4 +455,98 @@
|
|||
});
|
||||
};
|
||||
|
||||
// ===== Code Copy Functionality =====
|
||||
function initCodeBlocks() {
|
||||
const codeBlocks = document.querySelectorAll('.article-body pre');
|
||||
|
||||
codeBlocks.forEach((pre) => {
|
||||
// Проверяем, не добавлена ли уже кнопка
|
||||
if (pre.querySelector('.code-copy-btn')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const codeElement = pre.querySelector('code');
|
||||
if (!codeElement) return;
|
||||
|
||||
// Создаём кнопку копирования
|
||||
const copyButton = document.createElement('button');
|
||||
copyButton.className = 'code-copy-btn';
|
||||
copyButton.type = 'button';
|
||||
copyButton.setAttribute('aria-label', 'Копировать код');
|
||||
copyButton.innerHTML = '<i class="fas fa-copy"></i><span class="copy-text">Копировать</span>';
|
||||
|
||||
// Обработчик клика
|
||||
copyButton.addEventListener('click', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Получаем текст кода без HTML-тегов
|
||||
const codeText = codeElement.textContent || codeElement.innerText;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(codeText);
|
||||
|
||||
// Изменяем визуальное состояние кнопки
|
||||
copyButton.innerHTML = '<i class="fas fa-check"></i><span class="copy-text">Сохранено!</span>';
|
||||
copyButton.classList.add('copied');
|
||||
|
||||
// Возвращаем исходное состояние через 2 секунды
|
||||
setTimeout(() => {
|
||||
copyButton.innerHTML = '<i class="fas fa-copy"></i><span class="copy-text">Копировать</span>';
|
||||
copyButton.classList.remove('copied');
|
||||
}, 2000);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Ошибка при копировании:', err);
|
||||
|
||||
// Fallback для старых браузеров
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = codeText;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.left = '-999999px';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
copyButton.innerHTML = '<i class="fas fa-check"></i><span class="copy-text">Сохранено!</span>';
|
||||
copyButton.classList.add('copied');
|
||||
|
||||
setTimeout(() => {
|
||||
copyButton.innerHTML = '<i class="fas fa-copy"></i><span class="copy-text">Копировать</span>';
|
||||
copyButton.classList.remove('copied');
|
||||
}, 2000);
|
||||
} catch (err2) {
|
||||
showNotification('Не удалось скопировать код', 'error');
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
});
|
||||
|
||||
// Добавляем кнопку в блок кода
|
||||
pre.style.position = 'relative';
|
||||
pre.appendChild(copyButton);
|
||||
});
|
||||
}
|
||||
|
||||
function addHeaderAnchors() {
|
||||
const headers = document.querySelectorAll('.article-body h2, .article-body h3');
|
||||
headers.forEach(header => {
|
||||
const id = header.textContent
|
||||
.toLowerCase()
|
||||
.replace(/[^a-zа-яё0-9]+/g, '-')
|
||||
.replace(/^-|-$/g, '');
|
||||
|
||||
header.id = id;
|
||||
|
||||
const anchor = document.createElement('a');
|
||||
anchor.className = 'header-anchor';
|
||||
anchor.href = `#${id}`;
|
||||
anchor.innerHTML = '<i class="fas fa-link"></i>';
|
||||
anchor.setAttribute('aria-label', 'Ссылка на раздел');
|
||||
|
||||
header.appendChild(anchor);
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
@ -6,9 +6,7 @@
|
|||
<div class="auth-card" data-aos="fade-up">
|
||||
<div class="auth-header">
|
||||
<a href="{% url 'index' %}" class="logo">
|
||||
<span class="logo-deev">deev</span>
|
||||
<span class="logo-dot"></span>
|
||||
<span class="logo-space">space</span>
|
||||
<img src="{% static 'img/logo.png' %}" alt="deev.space" style="max-width: 130px; height: auto;">
|
||||
</a>
|
||||
<h1 class="auth-title">Вход в аккаунт</h1>
|
||||
<p class="auth-subtitle">Войдите, чтобы оставлять комментарии и оценивать статьи</p>
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@
|
|||
<div class="auth-card" data-aos="fade-up">
|
||||
<div class="auth-header">
|
||||
<a href="{% url 'index' %}" class="logo">
|
||||
<span class="logo-deev">deev</span>
|
||||
<span class="logo-dot"></span>
|
||||
<span class="logo-space">space</span>
|
||||
<img src="{% static 'img/logo.png' %}" alt="deev.space" style="max-width: 130px; height: auto;">
|
||||
</a>
|
||||
<h1 class="auth-title">Регистрация</h1>
|
||||
<p class="auth-subtitle">Создайте аккаунт для участия в обсуждениях</p>
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
|
||||
<!-- Article Content -->
|
||||
<div class="article-body" data-aos="fade-up" data-aos-delay="300">
|
||||
{{ article.post|safe|linebreaks }}
|
||||
{{ article.post|safe }}
|
||||
</div>
|
||||
|
||||
<!-- Image Gallery -->
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@
|
|||
<meta property="og:description" content="{{ page_description|default:global_settings.site_description }}">
|
||||
<meta property="og:type" content="{% block og_type %}website{% endblock %}">
|
||||
<meta property="og:url" content="https://deev.space{{ request.path }}">
|
||||
<meta property="og:image" content="{% block og_image %}{% static 'images/og-default.jpg' %}{% endblock %}">
|
||||
<meta property="og:site_name" content="{{ global_settings.site_name }}">
|
||||
<meta property="og:locale" content="ru_RU">
|
||||
|
||||
|
|
@ -32,7 +31,6 @@
|
|||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="{{ page_title|default:global_settings.owner_name }}">
|
||||
<meta name="twitter:description" content="{{ page_description|default:global_settings.site_description }}">
|
||||
<meta name="twitter:image" content="{% block twitter_image %}{% static 'images/og-default.jpg' %}{% endblock %}">
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href="https://deev.space{{ request.path }}">
|
||||
|
|
@ -55,6 +53,10 @@
|
|||
|
||||
<!-- AOS Animation -->
|
||||
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
|
||||
|
||||
<!-- Prism.js для подсветки синтаксиса -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.css">
|
||||
|
||||
<!-- Custom Styles -->
|
||||
<link rel="stylesheet" href="{% static 'css/variables.css' %}">
|
||||
|
|
@ -255,93 +257,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Footer -->
|
||||
<!-- <footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-brand">
|
||||
<a href="{% url 'index' %}" class="logo">
|
||||
|
||||
</a>
|
||||
<p>{{ global_settings.site_description|default:"Персональный сайт backend-разработчика. Создаю надёжные и масштабируемые решения на Python и Django." }}</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-nav">
|
||||
<h4>Навигация</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="{% url 'about' %}">Обо мне</a></li>
|
||||
<li><a href="{% url 'projects' %}">Проекты</a></li>
|
||||
<li><a href="{% url 'blog' %}">Блог</a></li>
|
||||
<li><a href="{% url 'achievements' %}">Достижения</a></li>
|
||||
<li><a href="{% url 'contacts' %}">Контакты</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-contact">
|
||||
<h4>Контакты</h4>
|
||||
<ul class="footer-links">
|
||||
<li>
|
||||
<a href="mailto:{{ global_settings.owner_email }}">
|
||||
<i class="fas fa-envelope"></i>
|
||||
{{ global_settings.owner_email }}
|
||||
</a>
|
||||
</li>
|
||||
{% if global_settings.owner_phone %}
|
||||
<li>
|
||||
<a href="tel:{{ global_settings.owner_phone|cut:' '|cut:'('|cut:')'|cut:'-' }}">
|
||||
<i class="fas fa-phone"></i>
|
||||
{{ global_settings.owner_phone }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<i class="fas fa-map-marker-alt"></i>
|
||||
{{ global_settings.owner_city }}, Россия
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-social">
|
||||
<h4>Социальные сети</h4>
|
||||
<div class="footer-socials">
|
||||
{% if global_settings.telegram_url %}
|
||||
<a href="{{ global_settings.telegram_url }}" target="_blank" rel="noopener noreferrer" class="footer-social-link" aria-label="Telegram">
|
||||
<i class="fab fa-telegram"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if global_settings.github_url %}
|
||||
<a href="{{ global_settings.github_url }}" target="_blank" rel="noopener noreferrer" class="footer-social-link" aria-label="GitHub">
|
||||
<i class="fab fa-github"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if global_settings.vk_url %}
|
||||
<a href="{{ global_settings.vk_url }}" target="_blank" rel="noopener noreferrer" class="footer-social-link" aria-label="VKontakte">
|
||||
<i class="fab fa-vk"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if global_settings.linkedin_url %}
|
||||
<a href="{{ global_settings.linkedin_url }}" target="_blank" rel="noopener noreferrer" class="footer-social-link" aria-label="LinkedIn">
|
||||
<i class="fab fa-linkedin"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="mailto:{{ global_settings.owner_email }}" class="footer-social-link" aria-label="Email">
|
||||
<i class="fas fa-envelope"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<p class="footer-copyright">
|
||||
© 2024-{% now "Y" %} {{ global_settings.owner_name }}. Все права защищены.
|
||||
</p>
|
||||
<p class="footer-made">
|
||||
Сделано с <i class="fas fa-heart" style="color: var(--primary-color);"></i> на Django
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer> -->
|
||||
|
||||
<!-- Back to Top -->
|
||||
<button class="back-to-top" id="backToTop" aria-label="Наверх">
|
||||
|
|
@ -351,6 +266,12 @@
|
|||
<!-- Scripts -->
|
||||
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
|
||||
<script src="{% static 'js/main.js' %}"></script>
|
||||
|
||||
<!-- Prism.js скрипты -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/normalize-whitespace/prism-normalize-whitespace.min.js"></script>
|
||||
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue