Редактор подробного описания
улучшил поведение.
This commit is contained in:
@@ -46,9 +46,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, onMounted, onUpdated, onUnmounted } from 'vue'
|
import { ref, watch, onMounted, onUpdated } from 'vue'
|
||||||
|
|
||||||
const STORAGE_KEY = 'taskboard_editor_height'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@@ -72,22 +70,7 @@ const props = defineProps({
|
|||||||
const emit = defineEmits(['update:modelValue', 'focus', 'blur'])
|
const emit = defineEmits(['update:modelValue', 'focus', 'blur'])
|
||||||
|
|
||||||
const editorRef = ref(null)
|
const editorRef = ref(null)
|
||||||
let resizeObserver = null
|
let isInternalChange = false
|
||||||
|
|
||||||
// Получить сохранённую высоту из 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))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Применить форматирование
|
// Применить форматирование
|
||||||
const applyFormat = (command) => {
|
const applyFormat = (command) => {
|
||||||
@@ -104,6 +87,7 @@ const syncContent = () => {
|
|||||||
html = html.replace(/<\/div><div>/gi, '<br>')
|
html = html.replace(/<\/div><div>/gi, '<br>')
|
||||||
html = html.replace(/<div>/gi, '')
|
html = html.replace(/<div>/gi, '')
|
||||||
html = html.replace(/<\/div>/gi, '')
|
html = html.replace(/<\/div>/gi, '')
|
||||||
|
isInternalChange = true
|
||||||
emit('update:modelValue', html)
|
emit('update:modelValue', html)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,8 +129,44 @@ const onKeydown = (e) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// После пробела или Enter — подсвечиваем ссылки
|
// Перехватываем Enter и вставляем <br> вручную
|
||||||
if (props.autoLinkify && (e.key === ' ' || e.key === 'Enter')) {
|
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)
|
setTimeout(linkifyContent, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,7 +225,11 @@ const setContent = (html) => {
|
|||||||
// Следим за внешними изменениями modelValue
|
// Следим за внешними изменениями modelValue
|
||||||
watch(() => props.modelValue, (newVal, oldVal) => {
|
watch(() => props.modelValue, (newVal, oldVal) => {
|
||||||
// Обновляем только если изменение пришло извне
|
// Обновляем только если изменение пришло извне
|
||||||
if (editorRef.value && editorRef.value.innerHTML !== newVal) {
|
if (isInternalChange) {
|
||||||
|
isInternalChange = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (editorRef.value) {
|
||||||
setContent(newVal)
|
setContent(newVal)
|
||||||
}
|
}
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
@@ -220,36 +244,10 @@ const refreshIcons = () => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
refreshIcons()
|
refreshIcons()
|
||||||
setContent(props.modelValue)
|
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)
|
onUpdated(refreshIcons)
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
if (resizeObserver) {
|
|
||||||
resizeObserver.disconnect()
|
|
||||||
resizeObserver = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Экспортируем методы для использования извне
|
// Экспортируем методы для использования извне
|
||||||
defineExpose({
|
defineExpose({
|
||||||
setContent,
|
setContent,
|
||||||
@@ -342,6 +340,11 @@ defineExpose({
|
|||||||
border-radius: 3px;
|
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) {
|
.rich-editor :deep(a) {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user