From 27a358a41f5c4ca3fd2f8fb33c37ca4636104ff1 Mon Sep 17 00:00:00 2001 From: Untone Date: Tue, 1 Jul 2025 09:32:22 +0300 Subject: [PATCH] panel-improves --- CHANGELOG.md | 12 +-- panel/modals/TopicBulkParentModal.tsx | 47 +++++------ panel/modals/TopicHierarchyModal.tsx | 99 ++++++++--------------- panel/modals/TopicMergeModal.tsx | 73 ++++++++--------- panel/modals/TopicParentModal.tsx | 23 ++---- panel/modals/TopicSimpleParentModal.tsx | 63 ++++++--------- panel/routes/invites.tsx | 28 +++---- panel/routes/topics.tsx | 45 ++++++----- panel/styles/CodePreview.module.css | 101 +++++++++++++++++++++-- panel/ui/EditableCodePreview.tsx | 103 +++++++----------------- 10 files changed, 284 insertions(+), 310 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20fad7fc..2fada541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,12 +14,11 @@ - **Компактный дизайн**: Уменьшены отступы (padding) для экономии места - **Улучшенная синхронизация скролла**: Номера строк синхронизируются со скроллом основного контента - **ИСПРАВЛЕНО**: Исправлена проблема с курсором в режиме редактирования - курсор теперь корректно перемещается при вводе текста и сохраняет позицию при обновлении содержимого - - **ИСПРАВЛЕНО**: Номера строк теперь правильно синхронизируются с содержимым - они прокручиваются вместе с текстом и показывают реальные номера строк документа - - **УЛУЧШЕНО**: Увеличена максимальная высота модальных окон с содержимым публикаций с 70vh до 85vh для более комфортного редактирования - - **ИСПРАВЛЕНО**: Убраны жесткие ограничения высоты в CSS (`min-height: 500px` в `.editableCodeContainer` и `min-height: 450px` в `.editorArea`) - теперь размер полностью контролируется параметром `maxHeight` - - **УЛУЧШЕНО**: Редактор кода теперь использует точную высоту `height: 85vh` вместо ограничений `min-height/max-height` для лучшего контроля размеров - - **ИСПРАВЛЕНО**: Модальное окно размера "large" теперь действительно занимает 85% высоты экрана (`height: 85vh, max-height: 85vh`) - - **УЛУЧШЕНО**: Содержимое модального окна использует `flex: 1` для заполнения всей доступной площади, убран padding для максимального использования пространства + - Номера строк теперь правильно синхронизируются с содержимым - они прокручиваются вместе с текстом и показывают реальные номера строк документа + - Увеличена высота модальных окон + - **УЛУЧШЕНО**: Уменьшена ширина области номеров строк с 50px до 24px для максимальной экономии места + - **ОПТИМИЗИРОВАНО**: Размер шрифта номеров строк уменьшен до 9px, padding уменьшен до 2px для компактности + - **УЛУЧШЕНО**: Содержимое сдвинуто ближе к левому краю (left: 24px), уменьшен padding с 12px до 8px для лучшего использования пространства - **Техническая архитектура**: - Функция `formatHtmlContent()` для автоматического форматирования HTML разметки - Функция `generateLineNumbers()` для генерации номеров строк @@ -28,6 +27,7 @@ - Улучшенная обработка различных типов контента (HTML/markup vs обычный текст) - Правильная работа с Selection API для сохранения позиции курсора в contentEditable элементах - Синхронизация содержимого редактируемой области без потери фокуса и позиции курсора + - **РЕФАКТОРИНГ СТИЛЕЙ**: Все inline стили перенесены в CSS модули для лучшей поддерживаемости кода ### Исправления авторизации diff --git a/panel/modals/TopicBulkParentModal.tsx b/panel/modals/TopicBulkParentModal.tsx index 821d8dc0..a93e4260 100644 --- a/panel/modals/TopicBulkParentModal.tsx +++ b/panel/modals/TopicBulkParentModal.tsx @@ -1,7 +1,7 @@ import { Component, createSignal, For, Show } from 'solid-js' +import styles from '../styles/Form.module.css' import Button from '../ui/Button' import Modal from '../ui/Modal' -import styles from '../styles/Form.module.css' interface Topic { id: number @@ -33,21 +33,19 @@ const TopicBulkParentModal: Component = (props) => { // Получаем выбранные топики const getSelectedTopics = () => { - return props.allTopics.filter(topic => - props.selectedTopicIds.includes(topic.id) - ) + return props.allTopics.filter((topic) => props.selectedTopicIds.includes(topic.id)) } // Фильтрация доступных родителей const getAvailableParents = () => { const selectedIds = new Set(props.selectedTopicIds) - return props.allTopics.filter(topic => { + return props.allTopics.filter((topic) => { // Исключаем выбранные топики if (selectedIds.has(topic.id)) return false // Исключаем топики, которые являются детьми выбранных - const isChildOfSelected = props.selectedTopicIds.some(selectedId => + const isChildOfSelected = props.selectedTopicIds.some((selectedId) => isDescendant(selectedId, topic.id) ) if (isChildOfSelected) return false @@ -62,7 +60,7 @@ const TopicBulkParentModal: Component = (props) => { // Проверка, является ли топик потомком другого const isDescendant = (ancestorId: number, descendantId: number): boolean => { - const descendant = props.allTopics.find(t => t.id === descendantId) + const descendant = props.allTopics.find((t) => t.id === descendantId) if (!descendant || !descendant.parent_ids) return false return descendant.parent_ids.includes(ancestorId) @@ -70,7 +68,7 @@ const TopicBulkParentModal: Component = (props) => { // Получение пути к корню const getTopicPath = (topicId: number): string => { - const topic = props.allTopics.find(t => t.id === topicId) + const topic = props.allTopics.find((t) => t.id === topicId) if (!topic) return '' if (!topic.parent_ids || topic.parent_ids.length === 0) { @@ -86,7 +84,7 @@ const TopicBulkParentModal: Component = (props) => { const selectedTopics = getSelectedTopics() const communities = new Map() - selectedTopics.forEach(topic => { + selectedTopics.forEach((topic) => { if (!communities.has(topic.community)) { communities.set(topic.community, []) } @@ -108,7 +106,7 @@ const TopicBulkParentModal: Component = (props) => { return 'Выберите родительскую тему' } - const selectedParent = props.allTopics.find(t => t.id === newParentId()) + const selectedParent = props.allTopics.find((t) => t.id === newParentId()) if (selectedParent) { const selectedCommunity = Array.from(communities.keys())[0] if (selectedParent.community !== selectedCommunity) { @@ -130,11 +128,11 @@ const TopicBulkParentModal: Component = (props) => { const changes: BulkParentChange[] = [] const selectedTopics = getSelectedTopics() - selectedTopics.forEach(topic => { + selectedTopics.forEach((topic) => { let newParentIds: number[] = [] if (actionType() === 'set' && newParentId()) { - const parentTopic = props.allTopics.find(t => t.id === newParentId()) + const parentTopic = props.allTopics.find((t) => t.id === newParentId()) if (parentTopic) { newParentIds = [...(parentTopic.parent_ids || []), newParentId()!] } @@ -161,7 +159,8 @@ const TopicBulkParentModal: Component = (props) => { {/* Проверка совместимости */} 1}>
- ⚠️ Выбраны темы из разных сообществ. Массовое изменение иерархии возможно только для тем одного сообщества. + ⚠️ Выбраны темы из разных сообществ. Массовое изменение иерархии возможно только для тем одного + сообщества.
@@ -175,9 +174,7 @@ const TopicBulkParentModal: Component = (props) => { {topic.title} #{topic.id} 0}> -
- Текущий путь: {getTopicPath(topic.id)} -
+
Текущий путь: {getTopicPath(topic.id)}
)} @@ -256,9 +253,7 @@ const TopicBulkParentModal: Component = (props) => { {topic.slug} 0}> -
- Текущий путь: {getTopicPath(topic.id)} -
+
Текущий путь: {getTopicPath(topic.id)}
@@ -270,8 +265,7 @@ const TopicBulkParentModal: Component = (props) => {
{searchQuery() ? `Нет доступных тем для поиска "${searchQuery()}"` - : 'Нет доступных родительских тем' - } + : 'Нет доступных родительских тем'}
@@ -292,11 +286,12 @@ const TopicBulkParentModal: Component = (props) => { - Станет: { - actionType() === 'makeRoot' - ? 'Корневая тема' - : newParentId() ? `${getTopicPath(newParentId()!)} → ${topic.title}` : '' - } + Станет:{' '} + {actionType() === 'makeRoot' + ? 'Корневая тема' + : newParentId() + ? `${getTopicPath(newParentId()!)} → ${topic.title}` + : ''} diff --git a/panel/modals/TopicHierarchyModal.tsx b/panel/modals/TopicHierarchyModal.tsx index 2d6c2d62..9a71541b 100644 --- a/panel/modals/TopicHierarchyModal.tsx +++ b/panel/modals/TopicHierarchyModal.tsx @@ -1,7 +1,7 @@ -import { Component, createSignal, For, Show } from 'solid-js' +import { Component, createSignal, For, JSX, Show } from 'solid-js' +import styles from '../styles/Form.module.css' import Button from '../ui/Button' import Modal from '../ui/Modal' -import styles from '../styles/Form.module.css' // Типы для топиков interface Topic { @@ -28,7 +28,7 @@ interface HierarchyChange { oldParentIds: number[] } -const TopicHierarchyModal: Component = (props) => { +const TopicHierarchyModal = (props: TopicHierarchyModalProps) => { const [localTopics, setLocalTopics] = createSignal([]) const [changes, setChanges] = createSignal([]) const [expandedNodes, setExpandedNodes] = createSignal>(new Set()) @@ -43,7 +43,7 @@ const TopicHierarchyModal: Component = (props) => { setSearchQuery('') setSelectedForMove(null) // Раскрываем все узлы по умолчанию - const allIds = new Set(props.topics.map(t => t.id)) + const allIds = new Set(props.topics.map((t) => t.id)) setExpandedNodes(allIds) } @@ -112,10 +112,8 @@ const TopicHierarchyModal: Component = (props) => { // Обновление родителя топика const updateTopicParent = (topicId: number, newParentIds: number[]) => { const flatTopics = flattenTopics(localTopics()) - const updatedTopics = flatTopics.map(topic => - topic.id === topicId - ? { ...topic, parent_ids: newParentIds } - : topic + const updatedTopics = flatTopics.map((topic) => + topic.id === topicId ? { ...topic, parent_ids: newParentIds } : topic ) const newHierarchy = buildHierarchy(updatedTopics) setLocalTopics(newHierarchy) @@ -125,7 +123,7 @@ const TopicHierarchyModal: Component = (props) => { const flattenTopics = (topics: Topic[]): Topic[] => { const result: Topic[] = [] const flatten = (topicList: Topic[]) => { - topicList.forEach(topic => { + topicList.forEach((topic) => { result.push(topic) if (topic.children) { flatten(topic.children) @@ -173,7 +171,9 @@ const TopicHierarchyModal: Component = (props) => { setSelectedForMove(topicId) const topic = findTopicById(topicId) if (topic) { - props.onError(`Выбрана тема "${topic.title}" для перемещения. Теперь нажмите на новую родительскую тему или используйте "Сделать корневой".`) + props.onError( + `Выбрана тема "${topic.title}" для перемещения. Теперь нажмите на новую родительскую тему или используйте "Сделать корневой".` + ) } } @@ -203,8 +203,8 @@ const TopicHierarchyModal: Component = (props) => { updateTopicParent(selectedId, newParentIds) // Добавляем в список изменений - setChanges(prev => [ - ...prev.filter(c => c.topicId !== selectedId), + setChanges((prev) => [ + ...prev.filter((c) => c.topicId !== selectedId), { topicId: selectedId, newParentIds, @@ -222,14 +222,14 @@ const TopicHierarchyModal: Component = (props) => { const expanded = expandedNodes() // Раскрываем всех родителей - topic.parent_ids.forEach(parentId => { + topic.parent_ids.forEach((parentId) => { expanded.add(parentId) }) setExpandedNodes(new Set(expanded)) } // Рендеринг дерева топиков - const renderTree = (topics: Topic[]): any => { + const renderTree = (topics: Topic[]): JSX.Element => { return ( {(topic) => { @@ -253,8 +253,12 @@ const TopicHierarchyModal: Component = (props) => { }} style={{ 'padding-left': `${(topic.level || 0) * 20}px`, - 'cursor': 'pointer', - 'border': isSelected ? '2px solid #007bff' : isTarget ? '2px dashed #28a745' : '1px solid transparent', + cursor: 'pointer', + border: isSelected + ? '2px solid #007bff' + : isTarget + ? '2px dashed #28a745' + : '1px solid transparent', 'background-color': isSelected ? '#e3f2fd' : isTarget ? '#d4edda' : 'transparent' }} > @@ -276,7 +280,7 @@ const TopicHierarchyModal: Component = (props) => { - + @@ -295,9 +299,7 @@ const TopicHierarchyModal: Component = (props) => { -
- {renderTree(topic.children!)} -
+
{renderTree(topic.children!)}
) @@ -306,23 +308,6 @@ const TopicHierarchyModal: Component = (props) => { ) } - // Сброс корневого уровня (перетаскивание в корень) - const makeRootTopic = (topicId: number) => { - updateTopicParent(topicId, []) - - const draggedTopic = findTopicById(topicId) - if (!draggedTopic) return - - setChanges(prev => [ - ...prev.filter(c => c.topicId !== topicId), - { - topicId, - newParentIds: [], - oldParentIds: draggedTopic.parent_ids || [] - } - ]) - } - // Сохранение изменений const handleSave = () => { if (changes().length === 0) { @@ -338,12 +323,7 @@ const TopicHierarchyModal: Component = (props) => { } return ( - +

Инструкции:

@@ -382,15 +362,11 @@ const TopicHierarchyModal: Component = (props) => {
-
- ❌ Тема не найдена -
+
❌ Тема не найдена
-
- {renderTree(localTopics())} -
+
{renderTree(localTopics())}
0}>
@@ -400,11 +376,10 @@ const TopicHierarchyModal: Component = (props) => { const topic = findTopicById(change.topicId) return (
- {topic?.title}: { - change.newParentIds.length === 0 - ? 'станет корневой темой' - : `переместится под тему #${change.newParentIds[change.newParentIds.length - 1]}` - } + {topic?.title}:{' '} + {change.newParentIds.length === 0 + ? 'станет корневой темой' + : `переместится под тему #${change.newParentIds[change.newParentIds.length - 1]}`}
) }} @@ -422,16 +397,10 @@ const TopicHierarchyModal: Component = (props) => {
- -
@@ -442,11 +411,7 @@ const TopicHierarchyModal: Component = (props) => { - diff --git a/panel/modals/TopicMergeModal.tsx b/panel/modals/TopicMergeModal.tsx index 0a4fa105..9e0bd26c 100644 --- a/panel/modals/TopicMergeModal.tsx +++ b/panel/modals/TopicMergeModal.tsx @@ -1,8 +1,8 @@ import { Component, createSignal, For, Show } from 'solid-js' +import { MERGE_TOPICS_MUTATION } from '../graphql/mutations' +import styles from '../styles/Form.module.css' import Button from '../ui/Button' import Modal from '../ui/Modal' -import styles from '../styles/Form.module.css' -import { MERGE_TOPICS_MUTATION } from '../graphql/mutations' // Типы для топиков interface Topic { @@ -44,10 +44,12 @@ const TopicMergeModal: Component = (props) => { * Получает токен авторизации из localStorage или cookie */ const getAuthTokenFromCookie = () => { - return document.cookie - .split('; ') - .find(row => row.startsWith('auth_token=')) - ?.split('=')[1] || '' + return ( + document.cookie + .split('; ') + .find((row) => row.startsWith('auth_token=')) + ?.split('=')[1] || '' + ) } /** @@ -55,9 +57,9 @@ const TopicMergeModal: Component = (props) => { */ const handleSourceTopicToggle = (topicId: number, checked: boolean) => { if (checked) { - setSourceTopicIds(prev => [...prev, topicId]) + setSourceTopicIds((prev) => [...prev, topicId]) } else { - setSourceTopicIds(prev => prev.filter(id => id !== topicId)) + setSourceTopicIds((prev) => prev.filter((id) => id !== topicId)) } } @@ -78,13 +80,13 @@ const TopicMergeModal: Component = (props) => { } // Проверяем что все темы принадлежат одному сообществу - const targetTopic = props.topics.find(t => t.id === target) + const targetTopic = props.topics.find((t) => t.id === target) if (!targetTopic) return false const targetCommunity = targetTopic.community - const sourcesTopics = props.topics.filter(t => sources.includes(t.id)) + const sourcesTopics = props.topics.filter((t) => sources.includes(t.id)) - return sourcesTopics.every(topic => topic.community === targetCommunity) + return sourcesTopics.every((topic) => topic.community === targetCommunity) } /** @@ -141,12 +143,12 @@ const TopicMergeModal: Component = (props) => { } const stats = mergeResult.stats as MergeStats - const statsText = stats ? - ` (перенесено ${stats.followers_moved} подписчиков, ${stats.publications_moved} публикаций, ${stats.drafts_moved} черновиков, удалено ${stats.source_topics_deleted} тем)` : '' + const statsText = stats + ? ` (перенесено ${stats.followers_moved} подписчиков, ${stats.publications_moved} публикаций, ${stats.drafts_moved} черновиков, удалено ${stats.source_topics_deleted} тем)` + : '' props.onSuccess(mergeResult.message + statsText) handleClose() - } catch (error) { const errorMessage = (error as Error).message setError(errorMessage) @@ -173,7 +175,7 @@ const TopicMergeModal: Component = (props) => { */ const getAvailableTargetTopics = () => { const sources = sourceTopicIds() - return props.topics.filter(topic => !sources.includes(topic.id)) + return props.topics.filter((topic) => !sources.includes(topic.id)) } /** @@ -181,26 +183,22 @@ const TopicMergeModal: Component = (props) => { */ const getAvailableSourceTopics = () => { const target = targetTopicId() - return props.topics.filter(topic => topic.id !== target) + return props.topics.filter((topic) => topic.id !== target) } return ( - +

Выбор целевой темы

- Выберите тему, в которую будут слиты остальные темы. Все подписчики и публикации будут перенесены в эту тему. + Выберите тему, в которую будут слиты остальные темы. Все подписчики и публикации будут + перенесены в эту тему.

= (props) => { - +
@@ -642,7 +649,7 @@ const TopicsRoute: Component = (props) => { setSelectedTopics([]) setGroupAction('') }} - topics={rawTopics().filter(topic => selectedTopics().includes(topic.id))} + topics={rawTopics().filter((topic) => selectedTopics().includes(topic.id))} onSuccess={(message) => { props.onSuccess(message) setSelectedTopics([]) diff --git a/panel/styles/CodePreview.module.css b/panel/styles/CodePreview.module.css index be25ebab..c8c8cf67 100644 --- a/panel/styles/CodePreview.module.css +++ b/panel/styles/CodePreview.module.css @@ -1,33 +1,45 @@ .codePreview { position: relative; - padding-left: 50px !important; + padding-left: 24px !important; background-color: #2d2d2d; color: #f8f8f2; tab-size: 2; line-height: 1.4; - border-radius: 4px; overflow: hidden; font-size: 12px; } .lineNumber { display: block; - padding: 0 8px; + padding: 0 2px; text-align: right; color: #555; background: #1e1e1e; user-select: none; font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; - font-size: 11px; + font-size: 9px; line-height: 1.4; - min-height: 16.8px; /* 12px * 1.4 line-height */ + min-height: 12.6px; /* 9px * 1.4 line-height */ border-right: 1px solid rgba(255, 255, 255, 0.1); opacity: 0.7; pointer-events: none; } .lineNumbersContainer { + position: absolute; + left: 0; + top: 0; + width: 24px; + height: 100%; + background: #1e1e1e; + border-right: 1px solid rgba(255, 255, 255, 0.1); overflow: hidden; + user-select: none; + padding: 8px 2px; + font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; + font-size: 9px; + line-height: 1.4; + text-align: right; } .lineNumbersContainer .lineNumber { @@ -48,14 +60,13 @@ color: #fff; padding: 2px 6px; border-radius: 4px; - z-index: 100; + z-index: 10; } /* Стили для EditableCodePreview */ .editableCodeContainer { position: relative; background-color: #2d2d2d; - border-radius: 6px; overflow: hidden; height: 100%; display: flex; @@ -132,15 +143,37 @@ } .syntaxHighlight { + position: absolute; + top: 0; + left: 24px; + right: 0; + bottom: 0; + pointer-events: none; + color: transparent; + background: transparent; + margin: 0; + padding: 8px 8px; width: 100%; height: 100%; tab-size: 2; font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; font-size: 12px; line-height: 1.4; + white-space: pre-wrap; + word-wrap: break-word; + overflow: hidden; + z-index: 0; } .editorArea { + position: absolute; + top: 0; + left: 24px; + right: 0; + bottom: 0; + z-index: 1; + margin: 0; + padding: 8px 8px; resize: none; border: none; width: 100%; @@ -149,12 +182,66 @@ font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; font-size: 12px; line-height: 1.4; + white-space: pre-wrap; + word-wrap: break-word; + overflow-y: auto; + outline: none; } .editorArea:focus { outline: none; } +.editorAreaEditing { + background: rgba(0, 0, 0, 0.02); + color: rgba(255, 255, 255, 0.9); + cursor: text; + caret-color: #fff; +} + +.editorAreaViewing { + background: transparent; + color: transparent; + cursor: default; + caret-color: transparent; +} + +.editorWrapperEditing { + border: 2px solid #007acc; +} + +.codePreviewContainer { + position: absolute; + top: 0; + left: 24px; + right: 0; + bottom: 0; + margin: 0; + padding: 8px 8px; + font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; + font-size: 12px; + line-height: 1.4; + white-space: pre-wrap; + word-wrap: break-word; + background: transparent; + cursor: pointer; + overflow-y: auto; + z-index: 2; +} + +.placeholder { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #666; + cursor: pointer; + font-style: italic; + font-size: 14px; + pointer-events: none; + user-select: none; +} + .placeholder { pointer-events: none; user-select: none; diff --git a/panel/ui/EditableCodePreview.tsx b/panel/ui/EditableCodePreview.tsx index 507caed1..2075f76c 100644 --- a/panel/ui/EditableCodePreview.tsx +++ b/panel/ui/EditableCodePreview.tsx @@ -28,10 +28,10 @@ const formatHtmlContent = (html: string): string => { if (!html || typeof html !== 'string') return '' // Удаляем лишние пробелы между тегами - let formatted = html - .replace(/>\s+<') // Убираем пробелы между тегами - .replace(/\s+/g, ' ') // Множественные пробелы в одиночные - .trim() // Убираем пробелы в начале и конце + const formatted = html + .replace(/>\s+<') // Убираем пробелы между тегами + .replace(/\s+/g, ' ') // Множественные пробелы в одиночные + .trim() // Убираем пробелы в начале и конце // Добавляем отступы для лучшего отображения const indent = ' ' @@ -117,7 +117,7 @@ const EditableCodePreview = (props: EditableCodePreviewProps) => { const lineNumbers = generateLineNumbers(content()) lineNumbersRef.innerHTML = lineNumbers - .map(num => `
${num}
`) + .map((num) => `
${num}
`) .join('') } @@ -243,9 +243,8 @@ const EditableCodePreview = (props: EditableCodePreviewProps) => { // Эффект для обновления контента при изменении props createEffect(() => { if (!isEditing()) { - const formattedContent = language() === 'markup' || language() === 'html' - ? formatHtmlContent(props.content) - : props.content + const formattedContent = + language() === 'markup' || language() === 'html' ? formatHtmlContent(props.content) : props.content setContent(formattedContent) updateHighlight() updateLineNumbers() @@ -301,9 +300,8 @@ const EditableCodePreview = (props: EditableCodePreviewProps) => { }) onMount(() => { - const formattedContent = language() === 'markup' || language() === 'html' - ? formatHtmlContent(props.content) - : props.content + const formattedContent = + language() === 'markup' || language() === 'html' ? formatHtmlContent(props.content) : props.content setContent(formattedContent) updateHighlight() updateLineNumbers() @@ -313,22 +311,17 @@ const EditableCodePreview = (props: EditableCodePreviewProps) => {
{/* Контейнер редактора - увеличиваем размер */}
{/* Номера строк */} -
+
{/* Подсветка синтаксиса (фон) - только в режиме редактирования */}
{/* Индикатор языка */} - - {language()} - + {language()} {/* Плейсхолдер */} -
setIsEditing(true)} - style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #666; cursor: pointer; font-style: italic; font-size: 14px;" - > +
setIsEditing(true)}> {props.placeholder || 'Нажмите для редактирования...'}
{/* Кнопки управления внизу */} - {props.showButtons !== false && ( -
- setIsEditing(true)}> - ✏️ Редактировать - - }> + +
+ setIsEditing(true)}> + ✏️ Редактировать + + } + >
- )} +
) }