Commit 66da5bdf authored by Яков's avatar Яков
Browse files

update fix issue

parent 5d537563
{
"name": "react-ag-qeditor",
"version": "1.1.75",
"version": "1.1.76",
"description": "WYSIWYG html editor",
"author": "atma",
"license": "MIT",
......
......@@ -3,8 +3,19 @@ import { ReactNodeViewRenderer, NodeViewWrapper, NodeViewContent } from '@tiptap
import React, { useEffect, useRef, useState } from 'react'
import { TextSelection, Plugin, PluginKey } from 'prosemirror-state'
const DragHandleIcon = () => (
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="3" cy="3" r="1.5" fill="currentColor"/>
<circle cx="7" cy="3" r="1.5" fill="currentColor"/>
<circle cx="3" cy="8" r="1.5" fill="currentColor"/>
<circle cx="7" cy="8" r="1.5" fill="currentColor"/>
<circle cx="3" cy="13" r="1.5" fill="currentColor"/>
<circle cx="7" cy="13" r="1.5" fill="currentColor"/>
</svg>
)
// React компонент NodeView
export const ToggleBlockComponent = ({node, updateAttributes, getPos, editor}) => {
export const ToggleBlockComponent = ({node, updateAttributes, getPos, editor, selected}) => {
const open = node.attrs.open
const title = node.attrs.title
......@@ -17,13 +28,14 @@ export const ToggleBlockComponent = ({node, updateAttributes, getPos, editor}) =
useEffect(() => {
if (measurerRef.current) {
const width = measurerRef.current.offsetWidth + 100 // небольшой запас
const width = measurerRef.current.offsetWidth + 100
setInputWidth(`${width}px`)
}
}, [title])
return (
<NodeViewWrapper className="toggle-block" data-open={open}>
{selected && (
<button
type="button"
onClick={(e) => {
......@@ -45,7 +57,7 @@ export const ToggleBlockComponent = ({node, updateAttributes, getPos, editor}) =
right: 4,
zIndex: 10,
background: 'white',
border: '1px solid #ccc',
border: '1px solid #d9d9d9',
borderRadius: '50%',
width: 20,
height: 20,
......@@ -58,9 +70,18 @@ export const ToggleBlockComponent = ({node, updateAttributes, getPos, editor}) =
>
×
</button>
)}
<div className="toggle-block-inner">
<div className="toggle-header-wrapper">
<div
className="toggle-drag-handle"
contentEditable={false}
data-drag-handle=""
title="Перетащить"
>
<DragHandleIcon />
</div>
<span
className="toggle-header-measurer"
ref={measurerRef}
......@@ -78,12 +99,10 @@ export const ToggleBlockComponent = ({node, updateAttributes, getPos, editor}) =
style={{ width: inputWidth }}
onFocus={(e) => {
if (title.trim() === 'Заголовок') {
// выделить весь текст
setTimeout(() => e.target.select(), 0)
}
}}
/>
</div>
</div>
<div
......@@ -111,7 +130,7 @@ export const ToggleBlockComponent = ({node, updateAttributes, getPos, editor}) =
firstBlock.type.name === 'paragraph' &&
firstBlock.textContent.trim() === 'Введите подробности...'
) {
const from = pos + 2 // +1 = paragraph, +1 = start of text inside it
const from = pos + 2
const to = from + firstBlock.textContent.length
const tr = editor.state.tr.setSelection(
......@@ -137,6 +156,7 @@ const ToggleBlock = Node.create({
name: 'toggleBlock',
group: 'block',
content: 'block+',
draggable: true,
addAttributes () {
return {
......@@ -153,7 +173,6 @@ const ToggleBlock = Node.create({
const titleEl = wrapper?.querySelector('.toggle-header')
const title = titleEl?.textContent?.trim() || 'Заголовок'
// удаляем заголовок из DOM, чтобы не попал в content
if (titleEl?.parentNode) {
titleEl.parentNode.removeChild(titleEl)
}
......@@ -166,9 +185,7 @@ const ToggleBlock = Node.create({
renderHTML ({HTMLAttributes}) {
return [
'div',
{
class: 'toggle-block',
},
{class: 'toggle-block'},
[
'div',
{class: 'toggle-block-inner'},
......@@ -184,32 +201,7 @@ const ToggleBlock = Node.create({
},
addProseMirrorPlugins () {
const buildGapFix = (state) => {
const doc = state.doc
const tr = state.tr
let additionalOffset = 0
let pos = 0
for (let i = 0; i < doc.childCount - 1; i++) {
const node = doc.child(i)
const nextNode = doc.child(i + 1)
pos += node.nodeSize
if (
node.type.name === 'toggleBlock' &&
nextNode.type.name === 'toggleBlock'
) {
tr.insert(
pos + additionalOffset,
state.schema.nodes.paragraph.create()
)
additionalOffset += 2
}
}
return additionalOffset > 0 ? tr : null
}
// Миграция: убираем пустые параграфы в начале toggle-блоков (артефакт старого бага)
const cleanLeadingEmptyParagraphs = (state) => {
const doc = state.doc
const tr = state.tr
......@@ -218,7 +210,6 @@ const ToggleBlock = Node.create({
doc.forEach((node, pos) => {
if (node.type.name !== 'toggleBlock') return
// Считаем сколько пустых параграфов стоят первыми подряд
let emptyCount = 0
for (let i = 0; i < node.childCount; i++) {
const child = node.child(i)
......@@ -229,12 +220,10 @@ const ToggleBlock = Node.create({
}
}
// Оставляем хотя бы один блок — удаляем только если после пустых есть что-то ещё
const removable = node.childCount > emptyCount ? emptyCount : Math.max(0, emptyCount - 1)
if (removable === 0) return
// Удаляем removable пустых параграфов с начала содержимого toggle-блока
const contentStart = pos + offset + 1 // +1 = открывающий токен toggle-блока
const contentStart = pos + offset + 1
let deleteEnd = contentStart
for (let i = 0; i < removable; i++) {
deleteEnd += node.child(i).nodeSize
......@@ -248,21 +237,12 @@ const ToggleBlock = Node.create({
return [
new Plugin({
key: new PluginKey('toggleBlockGap'),
key: new PluginKey('toggleBlockCleanup'),
view (editorView) {
// Миграция: убираем пустые параграфы в начале toggle-блоков (артефакт старого бага)
const cleanTr = cleanLeadingEmptyParagraphs(editorView.state)
if (cleanTr) editorView.dispatch(cleanTr)
// Fix adjacent toggle blocks present in initially loaded content
const tr = buildGapFix(editorView.state)
const tr = cleanLeadingEmptyParagraphs(editorView.state)
if (tr) editorView.dispatch(tr)
return {}
},
appendTransaction (transactions, _oldState, newState) {
if (!transactions.some(tr => tr.docChanged)) return null
return buildGapFix(newState)
}
}),
new Plugin({
key: new PluginKey('toggleBlockPaste'),
......@@ -271,7 +251,6 @@ const ToggleBlock = Node.create({
const { selection } = view.state
const $from = selection.$from
// Ищем toggleBlock в стеке предков
let toggleDepth = -1
for (let d = $from.depth; d > 0; d--) {
if ($from.node(d).type.name === 'toggleBlock') {
......@@ -281,15 +260,12 @@ const ToggleBlock = Node.create({
}
if (toggleDepth === -1) return false
// Только если есть заголовки — иначе стандартная обработка
let hasHeadings = false
slice.content.forEach(node => {
if (node.type.name === 'heading') hasHeadings = true
})
if (!hasHeadings) return false
// replaceSelection с openStart > 0 уходит выше toggleBlock и заменяет его.
// Вставляем узлы через tr.insert напрямую — позиция строго внутри toggleBlock.
let insertPos = $from.after(toggleDepth + 1)
const tr = view.state.tr
......@@ -300,7 +276,6 @@ const ToggleBlock = Node.create({
const nodes = []
slice.content.forEach(n => nodes.push(n))
// Вставляем в обратном порядке в одну позицию — итоговый порядок правильный
for (let i = nodes.length - 1; i >= 0; i--) {
tr.insert(insertPos, nodes[i])
}
......
......@@ -1642,10 +1642,34 @@ body{
}
.toggle-header-wrapper {
position: relative;
display: inline-block;
display: inline-flex;
align-items: center;
margin-bottom: 10px;
}
.toggle-drag-handle {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 24px;
margin-right: 4px;
color: #bbb;
cursor: grab;
flex-shrink: 0;
border-radius: 3px;
transition: color 0.15s;
&:active {
cursor: grabbing;
}
&:hover {
color: #888;
background: rgba(0,0,0,0.05);
}
}
.toggle-header-measurer {
visibility: hidden;
white-space: pre;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment