test push

This commit is contained in:
jze9
2026-05-18 18:51:00 +05:00
parent a64d8469df
commit 5057a61d55
5 changed files with 271 additions and 177 deletions

View File

@@ -12,7 +12,10 @@ export function MediaModal({ type, token, onSelect, onClose }) {
useEffect(() => {
api.listMedia(token, type)
.then(data => { setAllItems(data); setItems(data); setLoading(false) })
.then(data => {
const list = Array.isArray(data) ? data : (data.items ?? [])
setAllItems(list); setItems(list); setLoading(false)
})
.catch(() => setLoading(false))
}, [])

View File

@@ -35,60 +35,38 @@ function Sep() {
return <div className="w-px h-5 bg-slate-200 mx-0.5 flex-shrink-0" />
}
/* ── Hook: позиция дропдауна через fixed (вырывается из overflow-контейнеров) ── */
function useFixedDropdown() {
const [open, setOpen] = useState(false)
const [pos, setPos] = useState({ top: 0, left: 0 })
const btnRef = useRef(null)
const toggle = e => {
e.preventDefault()
if (!open && btnRef.current) {
const r = btnRef.current.getBoundingClientRect()
setPos({ top: r.bottom + 4, left: r.left })
}
setOpen(o => !o)
}
useEffect(() => {
if (!open) return
const close = e => {
if (!btnRef.current?.contains(e.target)) setOpen(false)
}
document.addEventListener('mousedown', close)
return () => document.removeEventListener('mousedown', close)
}, [open])
return { open, setOpen, pos, btnRef, toggle }
}
/* ── Font family dropdown ── */
function FontDropdown({ editor, fonts }) {
const { open, setOpen, pos, btnRef, toggle } = useFixedDropdown()
const [open, setOpen] = useState(false)
const ref = useRef(null)
const current = editor.getAttributes('textStyle').fontFamily || 'Шрифт'
useEffect(() => {
const h = e => { if (!ref.current?.contains(e.target)) setOpen(false) }
document.addEventListener('mousedown', h)
return () => document.removeEventListener('mousedown', h)
}, [])
return (
<div className="flex-shrink-0">
<div className="relative flex-shrink-0" ref={ref}>
<button
ref={btnRef}
type="button"
onMouseDown={toggle}
onMouseDown={e => { e.preventDefault(); setOpen(o => !o) }}
className="flex items-center gap-1 px-2 h-[30px] rounded-md text-xs text-slate-600 hover:bg-slate-100 border border-transparent hover:border-slate-200 transition-colors max-w-[130px] cursor-pointer"
title="Шрифт"
>
<span className="truncate leading-none" style={{ fontFamily: current !== 'Шрифт' ? current : undefined }}>
<span
className="truncate leading-none"
style={{ fontFamily: current !== 'Шрифт' ? current : undefined }}
>
{current}
</span>
<ChevronDown size={11} className="flex-shrink-0 opacity-60" />
</button>
{open && (
<div
style={{ position: 'fixed', top: pos.top, left: pos.left, zIndex: 9999, width: 208 }}
className="bg-white border border-slate-200 rounded-xl shadow-2xl max-h-72 overflow-y-auto py-1.5"
>
<div className="absolute top-[34px] left-0 z-50 bg-white border border-slate-200 rounded-xl shadow-2xl w-52 max-h-72 overflow-y-auto py-1.5">
<button
type="button"
className="w-full px-3 py-1.5 text-left text-xs text-slate-400 hover:bg-slate-50 transition-colors"
@@ -117,15 +95,21 @@ function FontDropdown({ editor, fonts }) {
/* ── Font size dropdown ── */
function SizeDropdown({ editor }) {
const { open, setOpen, pos, btnRef, toggle } = useFixedDropdown()
const [open, setOpen] = useState(false)
const ref = useRef(null)
const current = editor.getAttributes('textStyle').fontSize?.replace('px', '') || '—'
useEffect(() => {
const h = e => { if (!ref.current?.contains(e.target)) setOpen(false) }
document.addEventListener('mousedown', h)
return () => document.removeEventListener('mousedown', h)
}, [])
return (
<div className="flex-shrink-0">
<div className="relative flex-shrink-0" ref={ref}>
<button
ref={btnRef}
type="button"
onMouseDown={toggle}
onMouseDown={e => { e.preventDefault(); setOpen(o => !o) }}
className="flex items-center gap-1 px-1.5 h-[30px] w-14 rounded-md text-xs text-slate-600 hover:bg-slate-100 border border-transparent hover:border-slate-200 transition-colors cursor-pointer"
title="Размер шрифта"
>
@@ -134,10 +118,7 @@ function SizeDropdown({ editor }) {
</button>
{open && (
<div
style={{ position: 'fixed', top: pos.top, left: pos.left, zIndex: 9999, width: 80 }}
className="bg-white border border-slate-200 rounded-xl shadow-2xl max-h-56 overflow-y-auto py-1.5"
>
<div className="absolute top-[34px] left-0 z-50 bg-white border border-slate-200 rounded-xl shadow-2xl w-20 max-h-56 overflow-y-auto py-1.5">
{FONT_SIZES.map(s => (
<button
key={s}
@@ -158,23 +139,13 @@ function SizeDropdown({ editor }) {
function TableMenu({ editor }) {
const [open, setOpen] = useState(false)
const [pos, setPos] = useState({ top: 0, left: 0 })
const wrapRef = useRef(null)
const toggle = () => {
if (!open && wrapRef.current) {
const r = wrapRef.current.getBoundingClientRect()
setPos({ top: r.bottom + 4, left: r.left })
}
setOpen(o => !o)
}
const ref = useRef(null)
useEffect(() => {
if (!open) return
const close = e => { if (!wrapRef.current?.contains(e.target)) setOpen(false) }
document.addEventListener('mousedown', close)
return () => document.removeEventListener('mousedown', close)
}, [open])
const h = e => { if (!ref.current?.contains(e.target)) setOpen(false) }
document.addEventListener('mousedown', h)
return () => document.removeEventListener('mousedown', h)
}, [])
const items = [
{ label: 'Вставить таблицу 3×3', fn: () => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run() },
@@ -191,9 +162,9 @@ function TableMenu({ editor }) {
]
return (
<div ref={wrapRef} className="flex-shrink-0">
<div className="relative flex-shrink-0" ref={ref}>
<Btn
onClick={toggle}
onClick={() => setOpen(o => !o)}
active={editor.isActive('table')}
title="Таблица"
>
@@ -201,10 +172,7 @@ function TableMenu({ editor }) {
</Btn>
{open && (
<div
style={{ position: 'fixed', top: pos.top, left: pos.left, zIndex: 9999, width: 208 }}
className="bg-white border border-slate-200 rounded-xl shadow-2xl py-1.5"
>
<div className="absolute top-[34px] left-0 z-50 bg-white border border-slate-200 rounded-xl shadow-2xl w-52 py-1.5">
{items.map((item, i) =>
item === null ? (
<div key={i} className="my-1 border-t border-slate-100" />
@@ -213,7 +181,9 @@ function TableMenu({ editor }) {
key={item.label}
type="button"
className={`w-full px-3 py-1.5 text-left text-xs transition-colors
${item.danger ? 'text-red-500 hover:bg-red-50' : 'text-slate-700 hover:bg-slate-50'}`}
${item.danger
? 'text-red-500 hover:bg-red-50'
: 'text-slate-700 hover:bg-slate-50'}`}
onMouseDown={e => { e.preventDefault(); item.fn(); setOpen(false) }}
>
{item.label}
@@ -228,7 +198,7 @@ function TableMenu({ editor }) {
/* ── Main Toolbar ── */
export function Toolbar({ editor, onInsertImage, onInsertVideo, fonts }) {
export function Toolbar({ editor, onInsertImage, onInsertVideo, onEditHtml, htmlMode, fonts }) {
if (!editor) return null
const setLink = () => {
@@ -240,7 +210,7 @@ export function Toolbar({ editor, onInsertImage, onInsertVideo, fonts }) {
}
return (
<div className="flex items-center gap-0.5 px-3 py-1.5 bg-white border-b border-slate-200 shadow-sm overflow-x-auto flex-shrink-0 min-h-[46px]" style={{ scrollbarWidth: 'none' }}>
<div className="sticky top-0 z-10 flex items-center gap-0.5 px-3 py-1.5 bg-white border-b border-slate-200 shadow-sm overflow-x-auto flex-wrap min-h-[46px]">
{/* History */}
<Btn onClick={() => editor.chain().focus().undo().run()} disabled={!editor.can().undo()} title="Отменить (Ctrl+Z)">
<Undo2 size={14} />
@@ -305,9 +275,7 @@ export function Toolbar({ editor, onInsertImage, onInsertVideo, fonts }) {
<Btn onClick={() => editor.chain().focus().toggleBlockquote().run()} active={editor.isActive('blockquote')} title="Цитата">
<Quote size={14} />
</Btn>
<Btn onClick={() => editor.chain().focus().toggleCodeBlock().run()} active={editor.isActive('codeBlock')} title="Блок кода">
<Code2 size={14} />
</Btn>
<Btn onClick={() => editor.chain().focus().setHorizontalRule().run()} title="Разделитель">
<Minus size={14} />
</Btn>
@@ -324,6 +292,19 @@ export function Toolbar({ editor, onInsertImage, onInsertVideo, fonts }) {
<Video size={14} />
</Btn>
<TableMenu editor={editor} />
<button
type="button"
onMouseDown={e => { e.preventDefault(); onEditHtml?.() }}
title={htmlMode ? 'Вернуться в редактор (применить HTML)' : 'Редактировать HTML-код'}
style={{
height: 30, padding: '0 8px', borderRadius: 6, border: 'none', cursor: 'pointer',
fontFamily: 'monospace', fontSize: 12, fontWeight: 700, flexShrink: 0,
background: htmlMode ? '#4f46e5' : 'transparent',
color: htmlMode ? '#fff' : '#64748b',
}}
onMouseEnter={e => { if (!htmlMode) e.target.style.background = '#f1f5f9' }}
onMouseLeave={e => { if (!htmlMode) e.target.style.background = 'transparent' }}
>&lt;/&gt;</button>
<Sep />
{/* Typography */}