panel-upgrade-and-fixes
All checks were successful
Deploy on push / deploy (push) Successful in 7s

This commit is contained in:
2025-07-18 16:32:35 +03:00
parent 5d766b7601
commit 3826797317
16 changed files with 1071 additions and 1361 deletions

View File

@@ -167,10 +167,7 @@ const CollectionEditModal: Component<CollectionEditModalProps> = (props) => {
Описание
</span>
</label>
<HTMLEditor
value={formData().desc}
onInput={(value) => updateField('desc', value)}
/>
<HTMLEditor value={formData().desc} onInput={(value) => updateField('desc', value)} />
</div>
<div class={formStyles.fieldGroup}>

View File

@@ -9,9 +9,9 @@ import {
import formStyles from '../styles/Form.module.css'
import styles from '../styles/Modal.module.css'
import Button from '../ui/Button'
import HTMLEditor from '../ui/HTMLEditor'
import Modal from '../ui/Modal'
import RoleManager from '../ui/RoleManager'
import HTMLEditor from '../ui/HTMLEditor'
interface Community {
id: number
@@ -285,10 +285,7 @@ const CommunityEditModal = (props: CommunityEditModalProps) => {
Описание
</span>
</label>
<HTMLEditor
value={formData().desc || ''}
onInput={(value) => updateField('desc', value)}
/>
<HTMLEditor value={formData().desc || ''} onInput={(value) => updateField('desc', value)} />
</div>
<div class={formStyles.fieldGroup}>

View File

@@ -1,8 +1,8 @@
import { Component, createSignal, createEffect } from 'solid-js'
import { Component, createEffect, createSignal } from 'solid-js'
import styles from '../styles/Modal.module.css'
import Button from '../ui/Button'
import Modal from '../ui/Modal'
import HTMLEditor from '../ui/HTMLEditor'
import Modal from '../ui/Modal'
interface ReactionEditModalProps {
reaction: {
@@ -64,7 +64,7 @@ const ReactionEditModal: Component<ReactionEditModalProps> = (props) => {
const updateData: { id: number; body?: string; deleted_at?: number } = {
id: props.reaction.id,
body: body(),
body: body()
}
await props.onSave(updateData)
@@ -116,20 +116,11 @@ const ReactionEditModal: Component<ReactionEditModalProps> = (props) => {
return (
<Modal isOpen={props.isOpen} onClose={props.onClose} title="Редактирование реакции">
<div class={styles['modal-content']}>
{error() && (
<div class={styles['error-message']}>
{error()}
</div>
)}
{error() && <div class={styles['error-message']}>{error()}</div>}
<div class={styles['form-group']}>
<label class={styles['form-label']}>ID реакции:</label>
<input
type="text"
value={props.reaction.id}
disabled
class={styles['form-input']}
/>
<input type="text" value={props.reaction.id} disabled class={styles['form-input']} />
</div>
<div class={styles['form-group']}>

View File

@@ -1,11 +1,11 @@
import { createEffect, createSignal, For, Show, on } from 'solid-js'
import { createEffect, createSignal, on, Show } from 'solid-js'
import { Topic, useData } from '../context/data'
import { query } from '../graphql'
import { ADMIN_UPDATE_TOPIC_MUTATION } from '../graphql/mutations'
import styles from '../styles/Form.module.css'
import HTMLEditor from '../ui/HTMLEditor'
import Modal from '../ui/Modal'
import TopicPillsCloud, { type TopicPill } from '../ui/TopicPillsCloud'
import HTMLEditor from '../ui/HTMLEditor'
interface TopicEditModalProps {
topic: Topic
@@ -16,7 +16,7 @@ interface TopicEditModalProps {
}
export default function TopicEditModal(props: TopicEditModalProps) {
const { communities, topics, getCommunityName, selectedCommunity } = useData()
const { topics, getCommunityName, selectedCommunity } = useData()
// Состояние формы
const [formData, setFormData] = createSignal({
@@ -50,11 +50,16 @@ export default function TopicEditModal(props: TopicEditModalProps) {
})
// Обновление доступных родителей при изменении сообщества в форме
createEffect(on(() => formData().community, (communityId) => {
if (communityId > 0) {
updateAvailableParents(communityId)
}
}))
createEffect(
on(
() => formData().community,
(communityId) => {
if (communityId > 0) {
updateAvailableParents(communityId)
}
}
)
)
// Обновление доступных родителей при смене сообщества
const updateAvailableParents = (communityId: number, excludeTopicId?: number) => {
@@ -73,12 +78,12 @@ export default function TopicEditModal(props: TopicEditModalProps) {
* Преобразование Topic в TopicPill для компонента TopicPillsCloud
*/
const convertTopicsToTopicPills = (topics: Topic[]): TopicPill[] => {
return topics.map(topic => ({
return topics.map((topic) => ({
id: topic.id.toString(),
title: topic.title || '',
slug: topic.slug || '',
community: getCommunityName(topic.community),
parent_ids: (topic.parent_ids || []).map(id => id.toString()),
parent_ids: (topic.parent_ids || []).map((id) => id.toString())
}))
}
@@ -86,7 +91,7 @@ export default function TopicEditModal(props: TopicEditModalProps) {
* Обработка изменения выбора родительских топиков из таблеточек
*/
const handleParentSelectionChange = (selectedIds: string[]) => {
const parentIds = selectedIds.map(id => Number.parseInt(id))
const parentIds = selectedIds.map((id) => Number.parseInt(id))
setFormData((prev) => ({
...prev,
parent_ids: parentIds
@@ -219,10 +224,7 @@ export default function TopicEditModal(props: TopicEditModalProps) {
<div class={styles.field}>
<label class={styles.label}>
Описание:
<HTMLEditor
value={formData().body}
onInput={(value) => handleFieldChange('body', value)}
/>
<HTMLEditor value={formData().body} onInput={(value) => handleFieldChange('body', value)} />
</label>
</div>
</div>
@@ -233,19 +235,17 @@ export default function TopicEditModal(props: TopicEditModalProps) {
{/* Компонент с таблеточками для выбора родителей */}
<div class={styles.field}>
<TopicPillsCloud
topics={convertTopicsToTopicPills(availableParents())}
selectedTopics={formData().parent_ids.map(id => id.toString())}
onSelectionChange={handleParentSelectionChange}
excludeTopics={[formData().id.toString()]}
showSearch={true}
searchPlaceholder="Задайте родительские темы..."
hideSelectedInHeader={true}
/>
topics={convertTopicsToTopicPills(availableParents())}
selectedTopics={formData().parent_ids.map((id) => id.toString())}
onSelectionChange={handleParentSelectionChange}
excludeTopics={[formData().id.toString()]}
showSearch={true}
searchPlaceholder="Задайте родительские темы..."
hideSelectedInHeader={true}
/>
</div>
</div>
</Show>
</div>
</Modal>
</>

View File

@@ -53,11 +53,14 @@ const TopicMergeModal: Component<TopicMergeModalProps> = (props) => {
* Получает токен авторизации из localStorage или cookie
*/
const getAuthToken = () => {
return localStorage.getItem('auth_token') ||
document.cookie
.split('; ')
.find((row) => row.startsWith('auth_token='))
?.split('=')[1] || ''
return (
localStorage.getItem('auth_token') ||
document.cookie
.split('; ')
.find((row) => row.startsWith('auth_token='))
?.split('=')[1] ||
''
)
}
/**
@@ -91,10 +94,10 @@ const TopicMergeModal: Component<TopicMergeModalProps> = (props) => {
if (targetTopic) {
const targetCommunity = targetTopic.community
const invalidSources = sourcesTopics.filter(topic => topic.community !== targetCommunity)
const invalidSources = sourcesTopics.filter((topic) => topic.community !== targetCommunity)
if (invalidSources.length > 0) {
newErrors.general = `Все темы должны принадлежать одному сообществу. Темы ${invalidSources.map(t => `"${t.title}"`).join(', ')} принадлежат другому сообществу`
newErrors.general = `Все темы должны принадлежать одному сообществу. Темы ${invalidSources.map((t) => `"${t.title}"`).join(', ')} принадлежат другому сообществу`
}
}
}
@@ -117,9 +120,8 @@ const TopicMergeModal: Component<TopicMergeModalProps> = (props) => {
const query = searchQuery().toLowerCase().trim()
if (!query) return topicsList
return topicsList.filter(topic =>
topic.title?.toLowerCase().includes(query) ||
topic.slug?.toLowerCase().includes(query)
return topicsList.filter(
(topic) => topic.title?.toLowerCase().includes(query) || topic.slug?.toLowerCase().includes(query)
)
}
@@ -133,7 +135,7 @@ const TopicMergeModal: Component<TopicMergeModalProps> = (props) => {
// Убираем выбранную целевую тему из исходных тем
if (topicId) {
setSourceTopicIds(prev => prev.filter(id => id !== topicId))
setSourceTopicIds((prev) => prev.filter((id) => id !== topicId))
}
// Перевалидация
@@ -173,8 +175,8 @@ const TopicMergeModal: Component<TopicMergeModalProps> = (props) => {
if (!target || sources.length === 0) return null
const targetTopic = props.topics.find(t => t.id === target)
const sourceTopics = props.topics.filter(t => sources.includes(t.id))
const targetTopic = props.topics.find((t) => t.id === target)
const sourceTopics = props.topics.filter((t) => sources.includes(t.id))
const totalShouts = sourceTopics.reduce((sum, topic) => sum + (topic.stat?.shouts || 0), 0)
const totalFollowers = sourceTopics.reduce((sum, topic) => sum + (topic.stat?.followers || 0), 0)
@@ -286,20 +288,17 @@ const TopicMergeModal: Component<TopicMergeModalProps> = (props) => {
return (
<Modal isOpen={props.isOpen} onClose={handleClose} title="Слияние тем" size="large">
<div class={styles.form}>
{/* Общие ошибки */}
<Show when={errors().general}>
<div class={styles.formError}>
{errors().general}
</div>
<div class={styles.formError}>{errors().general}</div>
</Show>
{/* Выбор целевой темы */}
<div class={styles.section}>
<h3 class={styles.sectionTitle}>🎯 Целевая тема</h3>
<p class={styles.sectionDescription}>
Выберите тему, в которую будут слиты остальные темы. Все подписчики и публикации
будут перенесены в эту тему, а исходные темы будут удалены.
Выберите тему, в которую будут слиты остальные темы. Все подписчики и публикации будут
перенесены в эту тему, а исходные темы будут удалены.
</p>
<div class={styles.field}>
@@ -315,8 +314,7 @@ const TopicMergeModal: Component<TopicMergeModalProps> = (props) => {
<For each={getFilteredTopics(getAvailableTargetTopics())}>
{(topic) => (
<option value={topic.id}>
{topic.title} ({topic.slug})
{topic.stat ? `${topic.stat.shouts} публикаций` : ''}
{topic.title} ({topic.slug}){topic.stat ? `${topic.stat.shouts} публикаций` : ''}
</option>
)}
</For>
@@ -332,8 +330,8 @@ const TopicMergeModal: Component<TopicMergeModalProps> = (props) => {
<div class={styles.section}>
<h3 class={styles.sectionTitle}>📥 Исходные темы</h3>
<p class={styles.sectionDescription}>
Выберите темы, которые будут слиты в целевую тему. Все их данные будут перенесены,
а сами темы будут удалены.
Выберите темы, которые будут слиты в целевую тему. Все их данные будут перенесены, а сами темы
будут удалены.
</p>
<div class={styles.field}>
@@ -356,16 +354,16 @@ const TopicMergeModal: Component<TopicMergeModalProps> = (props) => {
<div class={styles.availableParents}>
<div class={styles.sectionHeader}>
<strong>Доступные темы для слияния:</strong>
<span class={styles.hint}>
Выбрано: {sourceTopicIds().length}
</span>
<span class={styles.hint}>Выбрано: {sourceTopicIds().length}</span>
</div>
<div class={styles.parentsGrid}>
<For each={getFilteredTopics(getAvailableSourceTopics())}>
{(topic) => {
const isChecked = () => sourceTopicIds().includes(topic.id)
const isDisabled = () => targetTopicId() && topic.community !== props.topics.find(t => t.id === targetTopicId())?.community
const isDisabled = () =>
targetTopicId() &&
topic.community !== props.topics.find((t) => t.id === targetTopicId())?.community
return (
<label
@@ -379,9 +377,7 @@ const TopicMergeModal: Component<TopicMergeModalProps> = (props) => {
onChange={(e) => handleSourceTopicToggle(topic.id, e.currentTarget.checked)}
/>
<div class={styles.parentLabel}>
<div class={styles.parentTitle}>
{topic.title}
</div>
<div class={styles.parentTitle}>{topic.title}</div>
<div class={styles.parentSlug}>{topic.slug}</div>
<div class={styles.parentStats}>
{getCommunityName(topic.community)}
@@ -401,12 +397,8 @@ const TopicMergeModal: Component<TopicMergeModalProps> = (props) => {
<Show when={getFilteredTopics(getAvailableSourceTopics()).length === 0}>
<div class={styles.noParents}>
<Show when={searchQuery()}>
Не найдено тем по запросу "{searchQuery()}"
</Show>
<Show when={!searchQuery()}>
Нет доступных тем для слияния
</Show>
<Show when={searchQuery()}>Не найдено тем по запросу "{searchQuery()}"</Show>
<Show when={!searchQuery()}>Нет доступных тем для слияния</Show>
</div>
</Show>
</div>
@@ -418,13 +410,13 @@ const TopicMergeModal: Component<TopicMergeModalProps> = (props) => {
<h3 class={styles.sectionTitle}>📊 Предварительный просмотр</h3>
<div class={styles.hierarchyPath}>
<div><strong>Целевая тема:</strong> {preview!.targetTopic!.title}</div>
<div>
<strong>Целевая тема:</strong> {preview!.targetTopic!.title}
</div>
<div class={styles.pathDisplay}>
<span>Слияние {preview!.sourcesCount} тем:</span>
<For each={preview!.sourceTopics}>
{(topic) => (
<span class={styles.pathItem}>{topic.title}</span>
)}
{(topic) => <span class={styles.pathItem}>{topic.title}</span>}
</For>
</div>
@@ -453,12 +445,10 @@ const TopicMergeModal: Component<TopicMergeModalProps> = (props) => {
onChange={(e) => setPreserveTarget(e.currentTarget.checked)}
/>
<div class={styles.parentLabel}>
<div class={styles.parentTitle}>
Сохранить свойства целевой темы
</div>
<div class={styles.parentTitle}>Сохранить свойства целевой темы</div>
<div class={styles.parentStats}>
Если включено, описание и другие свойства целевой темы не будут изменены.
Если выключено, свойства могут быть объединены с исходными темами.
Если включено, описание и другие свойства целевой темы не будут изменены. Если выключено,
свойства могут быть объединены с исходными темами.
</div>
</div>
</label>
@@ -467,12 +457,7 @@ const TopicMergeModal: Component<TopicMergeModalProps> = (props) => {
{/* Кнопки */}
<div class={styles.actions}>
<Button
type="button"
variant="secondary"
onClick={handleClose}
disabled={loading()}
>
<Button type="button" variant="secondary" onClick={handleClose} disabled={loading()}>
Отмена
</Button>
<Button