1
0

Редактор подробного описания

улучшил поведение.
This commit is contained in:
2026-01-14 11:58:58 +07:00
parent 9421f891d6
commit 6014dd94fc

View File

@@ -46,9 +46,7 @@
</template>
<script setup>
import { ref, watch, onMounted, onUpdated, onUnmounted } from 'vue'
const STORAGE_KEY = 'taskboard_editor_height'
import { ref, watch, onMounted, onUpdated } from 'vue'
const props = defineProps({
modelValue: {
@@ -72,22 +70,7 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue', 'focus', 'blur'])
const editorRef = ref(null)
let resizeObserver = null
// Получить сохранённую высоту из localStorage
function getSavedHeight() {
const saved = localStorage.getItem(STORAGE_KEY)
if (saved) {
const height = parseInt(saved, 10)
if (!isNaN(height) && height >= 120) return height
}
return null
}
// Сохранить высоту в localStorage
function saveHeight(height) {
localStorage.setItem(STORAGE_KEY, String(height))
}
let isInternalChange = false
// Применить форматирование
const applyFormat = (command) => {
@@ -104,6 +87,7 @@ const syncContent = () => {
html = html.replace(/<\/div><div>/gi, '<br>')
html = html.replace(/<div>/gi, '')
html = html.replace(/<\/div>/gi, '')
isInternalChange = true
emit('update:modelValue', html)
}
@@ -145,8 +129,44 @@ const onKeydown = (e) => {
}
}
// После пробела или Enter — подсвечиваем ссылки
if (props.autoLinkify && (e.key === ' ' || e.key === 'Enter')) {
// Перехватываем Enter и вставляем <br> вручную
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
const selection = window.getSelection()
if (!selection.rangeCount) return
const range = selection.getRangeAt(0)
range.deleteContents()
// Вставляем <br>
const br = document.createElement('br')
range.insertNode(br)
// Если мы в конце контента, добавляем ещё один <br> для видимости
const nextSibling = br.nextSibling
if (!nextSibling || (nextSibling.nodeType === Node.TEXT_NODE && !nextSibling.textContent)) {
const extraBr = document.createElement('br')
br.parentNode.insertBefore(extraBr, br.nextSibling)
}
// Перемещаем курсор после <br>
range.setStartAfter(br)
range.setEndAfter(br)
selection.removeAllRanges()
selection.addRange(range)
syncContent()
// Подсвечиваем ссылки после Enter
if (props.autoLinkify) {
setTimeout(linkifyContent, 0)
}
return
}
// После пробела — подсвечиваем ссылки
if (props.autoLinkify && e.key === ' ') {
setTimeout(linkifyContent, 0)
}
}
@@ -205,7 +225,11 @@ const setContent = (html) => {
// Следим за внешними изменениями modelValue
watch(() => props.modelValue, (newVal, oldVal) => {
// Обновляем только если изменение пришло извне
if (editorRef.value && editorRef.value.innerHTML !== newVal) {
if (isInternalChange) {
isInternalChange = false
return
}
if (editorRef.value) {
setContent(newVal)
}
}, { immediate: true })
@@ -220,36 +244,10 @@ const refreshIcons = () => {
onMounted(() => {
refreshIcons()
setContent(props.modelValue)
// Восстановить сохранённую высоту
const savedHeight = getSavedHeight()
if (savedHeight && editorRef.value) {
editorRef.value.style.height = savedHeight + 'px'
}
// Следить за изменением размера и сохранять
if (editorRef.value) {
resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const height = Math.round(entry.contentRect.height)
if (height >= 120) {
saveHeight(height)
}
}
})
resizeObserver.observe(editorRef.value)
}
})
onUpdated(refreshIcons)
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect()
resizeObserver = null
}
})
// Экспортируем методы для использования извне
defineExpose({
setContent,
@@ -342,6 +340,11 @@ defineExpose({
border-radius: 3px;
}
.rich-editor::-webkit-resizer {
background: linear-gradient(135deg, transparent 60%, rgba(255, 255, 255, 0.1) 60%, rgba(255, 255, 255, 0.1) 75%, transparent 75%, transparent 85%, rgba(255, 255, 255, 0.15) 85%);
border: none;
}
.rich-editor :deep(a) {
color: var(--accent);
text-decoration: none;