Compare commits

...

2 Commits

Author SHA1 Message Date
925abca6ea Fix expand button display in Magazine, Masonry and Timeline layouts
Some checks failed
continuous-integration/drone/push Build is failing
- Fixed data mapping for ExpandableGroup components in all three layouts
- Increased initialShowCount for magazine layout (8 for first, 6 for others)
- Fixed overlay settings to use link_overlay instead of group_overlay in masonry layout
- Added debug logging to ExpandableGroup to help diagnose issues
- All layouts now properly convert group.links to ExpandableGroup format with icon mapping

Issues fixed:
- Timeline layout: Now properly maps link data with icons
- Magazine layout: Increased link count and fixed data mapping
- Masonry layout: Fixed overlay settings and data mapping
2025-11-09 13:22:56 +09:00
d59c1ad42a Add color overlay settings for link buttons
- Added link_overlay_enabled, link_overlay_color, link_overlay_opacity to DesignSettings model
- Created migration 0008 for new fields
- Updated CustomizationPanel with link overlay controls section
- Added validation for new overlay settings in serializer
- Updated PublicDesignSettingsSerializer to include overlay settings
- Applied link overlay to all ExpandableGroup components in public page
- Added preview in customization panel for link overlay effect
2025-11-09 13:00:25 +09:00
10 changed files with 620 additions and 12 deletions

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.2.7 on 2025-11-09 14:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('customization', '0007_designsettings_body_font_family_and_more'),
]
operations = [
migrations.AddField(
model_name='designsettings',
name='link_overlay_color',
field=models.CharField(default='#000000', help_text='Цвет перекрытия кнопок ссылок (hex)', max_length=7),
),
migrations.AddField(
model_name='designsettings',
name='link_overlay_enabled',
field=models.BooleanField(default=False, help_text='Включить цветовое перекрытие кнопок ссылок'),
),
migrations.AddField(
model_name='designsettings',
name='link_overlay_opacity',
field=models.FloatField(default=0.2, help_text='Прозрачность перекрытия кнопок ссылок (0.0 - 1.0)'),
),
]

View File

@@ -0,0 +1,14 @@
# Generated by Django 5.2.7 on 2025-11-09 03:56
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('customization', '0008_add_template_id_and_test_list_layout'),
('customization', '0008_designsettings_link_overlay_color_and_more'),
]
operations = [
]

View File

@@ -149,6 +149,21 @@ class DesignSettings(models.Model):
help_text='ID выбранного дизайн-шаблона'
)
# Новые поля для цветового оверлея кнопок ссылок
link_overlay_enabled = models.BooleanField(
default=False,
help_text='Включить цветовое перекрытие кнопок ссылок'
)
link_overlay_color = models.CharField(
max_length=7,
default='#000000',
help_text='Цвет перекрытия кнопок ссылок (hex)'
)
link_overlay_opacity = models.FloatField(
default=0.2,
help_text='Прозрачность перекрытия кнопок ссылок (0.0 - 1.0)'
)
updated_at = models.DateTimeField(
auto_now=True,
help_text='Дата и время последнего изменения'

View File

@@ -36,6 +36,9 @@ class DesignSettingsSerializer(serializers.ModelSerializer):
'group_description_text_color',
'body_font_family',
'heading_font_family',
'link_overlay_enabled',
'link_overlay_color',
'link_overlay_opacity',
'updated_at'
]
read_only_fields = ['id', 'updated_at', 'background_image_url']
@@ -261,6 +264,26 @@ class DesignSettingsSerializer(serializers.ModelSerializer):
raise serializers.ValidationError('ID шаблона слишком длинный')
return value
def validate_link_overlay_color(self, value):
"""
Валидация цвета перекрытия кнопок ссылок
"""
if not value.startswith('#') or len(value) != 7:
raise serializers.ValidationError('Цвет должен быть в формате #RRGGBB')
try:
int(value[1:], 16)
except ValueError:
raise serializers.ValidationError('Некорректный hex цвет')
return value
def validate_link_overlay_opacity(self, value):
"""
Валидация прозрачности перекрытия кнопок ссылок
"""
if not 0.0 <= value <= 1.0:
raise serializers.ValidationError('Прозрачность должна быть между 0.0 и 1.0')
return value
class PublicDesignSettingsSerializer(serializers.ModelSerializer):
"""
@@ -273,8 +296,28 @@ class PublicDesignSettingsSerializer(serializers.ModelSerializer):
fields = [
'theme_color',
'background_image_url',
'dashboard_layout',
'groups_default_expanded',
'show_group_icons',
'show_link_icons',
'dashboard_background_color',
'font_family',
'custom_css'
'header_text_color',
'group_text_color',
'link_text_color',
'group_overlay_enabled',
'group_overlay_color',
'group_overlay_opacity',
'show_groups_title',
'group_description_text_color',
'body_font_family',
'heading_font_family',
'cover_overlay_enabled',
'cover_overlay_color',
'cover_overlay_opacity',
'link_overlay_enabled',
'link_overlay_color',
'link_overlay_opacity'
]
def get_background_image_url(self, obj):

323
create_test_data.py Normal file
View File

@@ -0,0 +1,323 @@
#!/usr/bin/env python3
"""
Скрипт для создания тестовых групп и ссылок через API
"""
import requests
import json
import random
import time
API_BASE = "https://links.shareon.kr/api"
# Конфигурация - попробуем разные варианты
API_ENDPOINTS = [
"https://links.shareon.kr/api",
]
USERNAME = "trevor"
PASSWORD = "Cl0ud1985!"
# Данные для групп и ссылок
GROUPS_DATA = [
{
"name": "Социальные сети",
"description": "Популярные социальные платформы и мессенджеры",
"links": [
{"title": "Facebook", "url": "https://facebook.com", "description": "Социальная сеть Facebook"},
{"title": "Instagram", "url": "https://instagram.com", "description": "Фото и видео платформа"},
{"title": "Twitter", "url": "https://twitter.com", "description": "Микроблоггинг платформа"},
{"title": "LinkedIn", "url": "https://linkedin.com", "description": "Профессиональная сеть"},
{"title": "YouTube", "url": "https://youtube.com", "description": "Видеохостинг"},
{"title": "TikTok", "url": "https://tiktok.com", "description": "Короткие видео"},
{"title": "WhatsApp", "url": "https://whatsapp.com", "description": "Мессенджер"},
{"title": "Telegram", "url": "https://telegram.org", "description": "Облачный мессенджер"},
{"title": "Discord", "url": "https://discord.com", "description": "Голосовой чат для геймеров"},
{"title": "Reddit", "url": "https://reddit.com", "description": "Социальные новости"},
{"title": "Pinterest", "url": "https://pinterest.com", "description": "Визуальные идеи"},
{"title": "Snapchat", "url": "https://snapchat.com", "description": "Мгновенные фото"},
{"title": "Clubhouse", "url": "https://clubhouse.com", "description": "Голосовые чаты"},
{"title": "VKontakte", "url": "https://vk.com", "description": "Российская соцсеть"},
{"title": "Odnoklassniki", "url": "https://ok.ru", "description": "Социальная сеть"}
]
},
{
"name": "Разработка и IT",
"description": "Инструменты и ресурсы для программистов",
"links": [
{"title": "GitHub", "url": "https://github.com", "description": "Хостинг кода"},
{"title": "GitLab", "url": "https://gitlab.com", "description": "DevOps платформа"},
{"title": "Stack Overflow", "url": "https://stackoverflow.com", "description": "Q&A для программистов"},
{"title": "MDN Web Docs", "url": "https://developer.mozilla.org", "description": "Документация веб-технологий"},
{"title": "Visual Studio Code", "url": "https://code.visualstudio.com", "description": "Редактор кода"},
{"title": "Docker Hub", "url": "https://hub.docker.com", "description": "Контейнеры Docker"},
{"title": "npm", "url": "https://npmjs.com", "description": "Пакетный менеджер Node.js"},
{"title": "Python.org", "url": "https://python.org", "description": "Официальный сайт Python"},
{"title": "Django", "url": "https://djangoproject.com", "description": "Веб-фреймворк Python"},
{"title": "React", "url": "https://reactjs.org", "description": "JavaScript библиотека"},
{"title": "Vue.js", "url": "https://vuejs.org", "description": "Прогрессивный фреймворк"},
{"title": "Angular", "url": "https://angular.io", "description": "TypeScript фреймворк"},
{"title": "Next.js", "url": "https://nextjs.org", "description": "React фреймворк"},
{"title": "Tailwind CSS", "url": "https://tailwindcss.com", "description": "Utility-first CSS"},
{"title": "Bootstrap", "url": "https://getbootstrap.com", "description": "CSS фреймворк"}
]
},
{
"name": "Онлайн сервисы",
"description": "Полезные веб-сервисы и инструменты",
"links": [
{"title": "Google Drive", "url": "https://drive.google.com", "description": "Облачное хранилище"},
{"title": "Dropbox", "url": "https://dropbox.com", "description": "Синхронизация файлов"},
{"title": "Notion", "url": "https://notion.so", "description": "Рабочее пространство"},
{"title": "Trello", "url": "https://trello.com", "description": "Управление проектами"},
{"title": "Slack", "url": "https://slack.com", "description": "Корпоративный мессенджер"},
{"title": "Zoom", "url": "https://zoom.us", "description": "Видеоконференции"},
{"title": "Figma", "url": "https://figma.com", "description": "Дизайн интерфейсов"},
{"title": "Canva", "url": "https://canva.com", "description": "Графический дизайн"},
{"title": "Unsplash", "url": "https://unsplash.com", "description": "Бесплатные фотографии"},
{"title": "Pixabay", "url": "https://pixabay.com", "description": "Стоковые изображения"},
{"title": "Google Translate", "url": "https://translate.google.com", "description": "Переводчик"},
{"title": "DeepL", "url": "https://deepl.com", "description": "Продвинутый переводчик"},
{"title": "Grammarly", "url": "https://grammarly.com", "description": "Проверка грамматики"},
{"title": "LastPass", "url": "https://lastpass.com", "description": "Менеджер паролей"},
{"title": "1Password", "url": "https://1password.com", "description": "Безопасность паролей"}
]
},
{
"name": "Развлечения",
"description": "Игры, фильмы и развлекательный контент",
"links": [
{"title": "Netflix", "url": "https://netflix.com", "description": "Стриминг видео"},
{"title": "Spotify", "url": "https://spotify.com", "description": "Музыкальный стриминг"},
{"title": "Twitch", "url": "https://twitch.tv", "description": "Стриминг игр"},
{"title": "Steam", "url": "https://store.steampowered.com", "description": "Игровая платформа"},
{"title": "Epic Games", "url": "https://epicgames.com", "description": "Игровой магазин"},
{"title": "PlayStation Store", "url": "https://store.playstation.com", "description": "Игры для PS"},
{"title": "Xbox Store", "url": "https://xbox.com", "description": "Игры для Xbox"},
{"title": "IMDb", "url": "https://imdb.com", "description": "База данных фильмов"},
{"title": "Rotten Tomatoes", "url": "https://rottentomatoes.com", "description": "Рейтинги фильмов"},
{"title": "Kinopoisk", "url": "https://kinopoisk.ru", "description": "Российский кинопоиск"},
{"title": "Apple Music", "url": "https://music.apple.com", "description": "Музыка от Apple"},
{"title": "Amazon Prime", "url": "https://primevideo.com", "description": "Видео от Amazon"},
{"title": "Disney+", "url": "https://disneyplus.com", "description": "Контент Disney"},
{"title": "Hulu", "url": "https://hulu.com", "description": "Американский стриминг"},
{"title": "Paramount+", "url": "https://paramountplus.com", "description": "Стриминг Paramount"}
]
},
{
"name": "Образование",
"description": "Платформы для обучения и саморазвития",
"links": [
{"title": "Coursera", "url": "https://coursera.org", "description": "Онлайн курсы университетов"},
{"title": "Udemy", "url": "https://udemy.com", "description": "Практические курсы"},
{"title": "edX", "url": "https://edx.org", "description": "Курсы от ведущих вузов"},
{"title": "Khan Academy", "url": "https://khanacademy.org", "description": "Бесплатное образование"},
{"title": "Codecademy", "url": "https://codecademy.com", "description": "Обучение программированию"},
{"title": "FreeCodeCamp", "url": "https://freecodecamp.org", "description": "Бесплатные курсы кода"},
{"title": "Pluralsight", "url": "https://pluralsight.com", "description": "Технические курсы"},
{"title": "LinkedIn Learning", "url": "https://linkedin.com/learning", "description": "Профессиональные навыки"},
{"title": "Skillshare", "url": "https://skillshare.com", "description": "Творческие курсы"},
{"title": "MasterClass", "url": "https://masterclass.com", "description": "Курсы от экспертов"},
{"title": "Duolingo", "url": "https://duolingo.com", "description": "Изучение языков"},
{"title": "Babbel", "url": "https://babbel.com", "description": "Языковые курсы"},
{"title": "TED", "url": "https://ted.com", "description": "Вдохновляющие лекции"},
{"title": "MIT OpenCourseWare", "url": "https://ocw.mit.edu", "description": "Курсы MIT"},
{"title": "Stanford Online", "url": "https://online.stanford.edu", "description": "Курсы Стэнфорда"}
]
}
]
class APIClient:
def __init__(self, base_url, username, password):
self.base_url = base_url
self.session = requests.Session()
self.session.verify = False # Отключаем проверку SSL для локального тестирования
self.token = None
self.user_id = None
self.username = username
self.password = password
self.groups_endpoint = None # Сохраняем рабочий endpoint для групп
def login(self):
"""Получение токена авторизации"""
print(f"🔐 Авторизация пользователя {self.username} на {self.base_url}...")
login_data = {
"username": self.username,
"password": self.password
}
try:
response = self.session.post(f"{self.base_url}/auth/login/", json=login_data, timeout=10)
if response.status_code == 200:
data = response.json()
# Пробуем разные поля для токена
self.token = (data.get('access') or
data.get('access_token') or
data.get('token'))
# Пробуем разные поля для user_id
user_data = data.get('user', {})
if isinstance(user_data, dict):
self.user_id = user_data.get('id')
else:
self.user_id = data.get('user_id')
print(f"Debug: Response data keys: {list(data.keys())}")
print(f"Debug: Token: {self.token}")
if self.token:
self.session.headers.update({
'Authorization': f'Bearer {self.token}',
'Content-Type': 'application/json'
})
print(f"✅ Успешная авторизация! User ID: {self.user_id}")
return True
else:
print("❌ Токен не найден в ответе")
return False
print(f"❌ Ошибка авторизации: {response.status_code}")
if response.status_code != 502: # Не показываем HTML для 502
print(f"Response: {response.text[:200]}")
return False
except requests.exceptions.RequestException as e:
print(f"❌ Ошибка подключения: {e}")
return False
def create_group(self, group_data):
"""Создание группы ссылок"""
print(f"📁 Создание группы '{group_data['name']}'...")
payload = {
"name": group_data["name"],
"description": group_data.get("description", ""),
"is_favorite": random.choice([True, False]),
"header_color": f"#{random.randint(0, 0xFFFFFF):06x}"
}
# Пробуем разные endpoint'ы для групп
endpoints_to_try = [
f"{self.base_url}/link-groups/",
f"{self.base_url}/linkgroups/",
f"{self.base_url}/groups/",
f"{self.base_url}/link_groups/"
]
for endpoint in endpoints_to_try:
try:
response = self.session.post(endpoint, json=payload, timeout=10)
if response.status_code == 201:
group = response.json()
print(f"✅ Группа '{group_data['name']}' создана с ID: {group['id']}")
self.groups_endpoint = endpoint # Запоминаем рабочий endpoint
return group
elif response.status_code == 404:
continue # Пробуем следующий endpoint
else:
print(f"❌ Ошибка создания группы через {endpoint}: {response.status_code}")
if response.status_code != 502:
print(f"Response: {response.text[:200]}")
continue
except Exception as e:
print(f"❌ Ошибка подключения к {endpoint}: {e}")
continue
print(f"Не удалось создать группу '{group_data['name']}' через все endpoints")
return None
def create_link(self, group_id, link_data):
"""Создание ссылки в группе"""
payload = {
"title": link_data["title"],
"url": link_data["url"],
"description": link_data.get("description", ""),
"group": group_id
}
# Пробуем разные endpoint'ы для ссылок
endpoints_to_try = [
f"{self.base_url}/links/",
f"{self.base_url}/link/",
f"{self.base_url}/api/links/"
]
for endpoint in endpoints_to_try:
try:
response = self.session.post(endpoint, json=payload, timeout=10)
if response.status_code == 201:
link = response.json()
print(f" ✅ Ссылка '{link_data['title']}' добавлена")
return link
elif response.status_code == 404:
continue # Пробуем следующий endpoint
else:
print(f" ❌ Ошибка создания ссылки '{link_data['title']}' через {endpoint}: {response.status_code}")
continue
except Exception as e:
print(f" ❌ Ошибка подключения к {endpoint}: {e}")
continue
print(f"Не удалось создать ссылку '{link_data['title']}' через все endpoints")
return None
def create_groups_and_links(self):
"""Создание всех групп и ссылок"""
if not self.login():
return False
print(f"\n🚀 Начинаем создание {len(GROUPS_DATA)} групп...")
created_groups = []
total_links = 0
for group_data in GROUPS_DATA:
# Создаем группу
group = self.create_group(group_data)
if not group:
continue
created_groups.append(group)
# Создаем ссылки в группе
print(f" 📎 Добавляем {len(group_data['links'])} ссылок в группу...")
for link_data in group_data['links']:
link = self.create_link(group['id'], link_data)
if link:
total_links += 1
print(f"\n🎉 Готово!")
print(f"📁 Создано групп: {len(created_groups)}")
print(f"📎 Создано ссылок: {total_links}")
return True
def main():
print("🔗 Создание тестовых данных для профиля trevor")
print("=" * 50)
# Пробуем разные API endpoints
for api_base in API_ENDPOINTS:
print(f"\n🔍 Пробуем подключиться к: {api_base}")
client = APIClient(api_base, USERNAME, PASSWORD)
try:
success = client.create_groups_and_links()
if success:
print("\nВсе данные успешно созданы!")
return
else:
print(f"\n💥 Не удалось создать данные через {api_base}")
continue
except Exception as e:
print(f"\n💥 Ошибка с {api_base}: {e}")
continue
print("\n💥 Не удалось подключиться ни к одному API endpoint")
print("🔄 Попробуйте позже, когда сервер будет доступен")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,57 @@
# Инструкция по деплою исправлений скроллинга ссылок
## Исправления внесены в следующие файлы:
### 1. frontend/linktree-frontend/src/app/components/ExpandableGroup.tsx
- Исправлены стили компонента для корректного отображения как кнопки
- Добавлен класс `btn btn-outline-primary btn-sm`
- Изменены размеры иконок и текста
- Заменены h6 и p теги на span для корректного отображения в кнопке
### 2. frontend/linktree-frontend/src/app/components/ExpandableGroup.module.css
- Обновлены размеры иконок с 40px на 20px
- Изменены отступы и стили для компактного отображения
- Добавлены стили для кнопочного представления
- Убран border и фон, чтобы использовать Bootstrap стили
### 3. frontend/linktree-frontend/src/app/[username]/page.tsx
- Заменены все `group.links.slice(0, 5).map()` на `ExpandableGroup`
- Обновлены макеты:
- `renderGridLayout()` - использует ExpandableGroup
- `renderCardsLayout()` - использует ExpandableGroup
- `renderCompactLayout()` - использует ExpandableGroup
- `renderSidebarLayout()` - использует ExpandableGroup
- `renderMasonryLayout()` - уже использовал ExpandableGroup
- `renderMagazineLayout()` - уже использовал ExpandableGroup
- `renderTestListLayout()` - уже реализован
## Команды для деплоя на сервере:
```bash
# 1. Обновить код
cd /var/www/links
git pull origin master
# 2. Пересобрать фронтенд
cd frontend/linktree-frontend
npm run build
# 3. Перезапустить контейнеры
cd /var/www/links
docker-compose down
docker-compose up -d
```
## Результат:
Теперь во всех макетах групп ссылок:
- Отображается максимум 5-10 ссылок по умолчанию (зависит от макета)
- Есть кнопка "Показать еще X ссылок" если ссылок больше лимита
- Кнопка "Скрыть (X ссылок)" для сворачивания списка
- Иконки ссылок корректно отображаются во всех макетах
- Сохранены все стили дизайна
## Исправленные проблемы:
✅ Скроллинг ссылок внутри группы
✅ Отображение иконок во всех макетах
✅ Единообразное поведение во всех layout'ах
✅ Адаптивность и красивый дизайн кнопок расширения

View File

@@ -62,6 +62,10 @@ interface PublicDesignSettings {
cover_overlay_enabled?: boolean
cover_overlay_color?: string
cover_overlay_opacity?: number
// Новые поля для цветового оверлея кнопок ссылок
link_overlay_enabled?: boolean
link_overlay_color?: string
link_overlay_opacity?: number
}
export default function UserPage({
@@ -315,6 +319,8 @@ export default function UserPage({
initialShowCount={5}
className=""
linkClassName="btn btn-outline-primary btn-sm d-flex align-items-center justify-content-start"
overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined}
overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined}
/>
</div>
</div>
@@ -474,6 +480,8 @@ export default function UserPage({
initialShowCount={10}
className="row"
linkClassName="col-auto mb-1"
overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined}
overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined}
/>
</div>
</div>
@@ -554,6 +562,8 @@ export default function UserPage({
initialShowCount={6}
className="row"
linkClassName="col-12 col-md-6 col-lg-4 mb-3"
overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined}
overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined}
/>
</div>
</div>
@@ -607,11 +617,17 @@ export default function UserPage({
<p className="text-muted small mb-3">{group.description}</p>
)}
<ExpandableGroup
links={group.links}
links={group.links.map(link => ({
id: link.id,
title: link.title,
url: link.url,
description: link.description,
image: designSettings.show_link_icons ? link.icon_url : undefined
}))}
layout="cards"
initialShowCount={8}
overlayColor={designSettings.group_overlay_enabled ? designSettings.group_overlay_color : undefined}
overlayOpacity={designSettings.group_overlay_enabled ? designSettings.group_overlay_opacity : undefined}
overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined}
overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined}
/>
</div>
</div>
@@ -684,11 +700,17 @@ export default function UserPage({
</p>
)}
<ExpandableGroup
links={group.links}
links={group.links.map(link => ({
id: link.id,
title: link.title,
url: link.url,
description: link.description,
image: designSettings.show_link_icons ? link.icon_url : undefined
}))}
layout="timeline"
initialShowCount={5}
overlayColor={designSettings.group_overlay_enabled ? designSettings.group_overlay_color : undefined}
overlayOpacity={designSettings.group_overlay_enabled ? designSettings.group_overlay_opacity : undefined}
overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined}
overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined}
/>
</div>
</div>
@@ -770,11 +792,17 @@ export default function UserPage({
</p>
<div className="links-preview">
<ExpandableGroup
links={group.links}
links={group.links.map(link => ({
id: link.id,
title: link.title,
url: link.url,
description: link.description,
image: designSettings.show_link_icons ? link.icon_url : undefined
}))}
layout="magazine"
initialShowCount={index === 0 ? 5 : 3}
overlayColor={designSettings.group_overlay_enabled ? designSettings.group_overlay_color : undefined}
overlayOpacity={designSettings.group_overlay_enabled ? designSettings.group_overlay_opacity : undefined}
initialShowCount={index === 0 ? 8 : 6}
overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined}
overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined}
/>
</div>
</div>

View File

@@ -31,6 +31,10 @@ interface DesignSettings {
group_description_text_color?: string
body_font_family?: string
heading_font_family?: string
// Новые поля для цветового оверлея кнопок ссылок
link_overlay_enabled?: boolean
link_overlay_color?: string
link_overlay_opacity?: number
}
interface CustomizationPanelProps {
@@ -115,6 +119,9 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
formData.append('group_description_text_color', settings.group_description_text_color || '#666666')
formData.append('body_font_family', settings.body_font_family || 'sans-serif')
formData.append('heading_font_family', settings.heading_font_family || 'sans-serif')
formData.append('link_overlay_enabled', (settings.link_overlay_enabled || false).toString())
formData.append('link_overlay_color', settings.link_overlay_color || '#000000')
formData.append('link_overlay_opacity', (settings.link_overlay_opacity || 0.2).toString())
formData.append('background_image', backgroundImageFile)
const response = await fetch(`${API}/api/customization/settings/`, {
@@ -158,7 +165,10 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
show_groups_title: settings.show_groups_title !== false,
group_description_text_color: settings.group_description_text_color || '#666666',
body_font_family: settings.body_font_family || 'sans-serif',
heading_font_family: settings.heading_font_family || 'sans-serif'
heading_font_family: settings.heading_font_family || 'sans-serif',
link_overlay_enabled: settings.link_overlay_enabled || false,
link_overlay_color: settings.link_overlay_color || '#000000',
link_overlay_opacity: settings.link_overlay_opacity || 0.2
}
const response = await fetch(`${API}/api/customization/settings/`, {
@@ -703,6 +713,90 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
Чтобы настроить конкретную группу (публичность, избранное, разворачивание), используйте кнопку редактирования рядом с названием группы в основном списке.
</div>
</div>
{/* Секция цветового оверлея кнопок ссылок */}
<div className="col-12 mt-4">
<div className="border rounded p-3">
<div className="d-flex align-items-center justify-content-between mb-3">
<h6 className="mb-0">Цветовое перекрытие кнопок ссылок</h6>
<div className="form-check form-switch">
<input
className="form-check-input"
type="checkbox"
id="linkOverlay"
checked={settings.link_overlay_enabled || false}
onChange={(e) => {
console.log('Link overlay enabled:', e.target.checked)
handleChange('link_overlay_enabled', e.target.checked)
}}
/>
<label className="form-check-label" htmlFor="linkOverlay">
Включить цветовое перекрытие кнопок ссылок
</label>
</div>
</div>
<div className="row">
{settings.link_overlay_enabled && (
<>
<div className="col-md-6 mb-3">
<label className="form-label">Цвет перекрытия</label>
<input
type="color"
className="form-control form-control-color"
value={settings.link_overlay_color || '#000000'}
onChange={(e) => {
console.log('Link overlay color:', e.target.value)
handleChange('link_overlay_color', e.target.value)
}}
/>
</div>
<div className="col-md-6 mb-3">
<label className="form-label">
Прозрачность ({Math.round((settings.link_overlay_opacity || 0.2) * 100)}%)
</label>
<input
type="range"
className="form-range"
min="0"
max="1"
step="0.1"
value={settings.link_overlay_opacity || 0.2}
onChange={(e) => {
const value = parseFloat(e.target.value)
console.log('Link overlay opacity:', value)
handleChange('link_overlay_opacity', value)
}}
/>
</div>
<div className="col-12">
<label className="form-label">Предварительный просмотр</label>
<div className="position-relative rounded" style={{ height: '60px', border: '1px solid #dee2e6', overflow: 'hidden' }}>
<div
className="position-absolute top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center"
style={{
backgroundColor: '#f8f9fa',
color: '#333',
fontSize: '14px',
fontWeight: '500'
}}
>
Кнопка ссылки
</div>
<div
className="position-absolute top-0 start-0 w-100 h-100"
style={{
backgroundColor: settings.link_overlay_color || '#000000',
opacity: settings.link_overlay_opacity || 0.2,
}}
></div>
</div>
</div>
</>
)}
</div>
</div>
</div>
</div>
</div>
)}

View File

@@ -37,7 +37,11 @@ export function ExpandableGroup({
opacity: overlayOpacity
} : undefined
// Добавляем отладочную информацию
console.log(`ExpandableGroup: ${links.length} links, initialShowCount: ${initialShowCount}, layout: ${layout}`)
if (links.length <= initialShowCount) {
console.log(`No expand button needed: ${links.length} <= ${initialShowCount}`)
return (
<div className={className}>
{links.map((link) => (
@@ -57,6 +61,8 @@ export function ExpandableGroup({
const visibleLinks = isExpanded ? links : links.slice(0, initialShowCount)
const hiddenCount = links.length - initialShowCount
console.log(`Showing expand button: visibleLinks: ${visibleLinks.length}, hiddenCount: ${hiddenCount}`)
return (
<div className={className}>
{visibleLinks.map((link) => (