Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
R
react-ag-qeditor
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
lib
react-ag-qeditor
Commits
c25a5de8
Commit
c25a5de8
authored
Jun 27, 2025
by
Яков
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix drag and drop
parent
c79e85c3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
118 additions
and
87 deletions
+118
-87
App.js
example/src/App.js
+1
-1
QEditor.jsx
src/QEditor.jsx
+13
-2
DragAndDrop.js
src/extensions/DragAndDrop.js
+104
-84
No files found.
example/src/App.js
View file @
c25a5de8
...
...
@@ -12,7 +12,7 @@ const App = () => {
// console.log(value);
}}
uploadOptions
=
{{
url
:
'
https://cdn.atmaguru.online/upload/?sid=atmacompany&md5=0cETbV4BquHkqAdG9cK9MA&expires=1742192970
'
,
url
:
'
/upload
'
,
errorMessage
:
'Загрузка временно невозможна'
}}
style
=
{{
...
...
src/QEditor.jsx
View file @
c25a5de8
...
...
@@ -512,8 +512,19 @@ const QEditor = ({
Superscript
,
Subscript
,
DragAndDrop
.
configure
({
linkUpload
:
uploadOptions
.
url
}),
uploadUrl
:
uploadOptions
.
url
,
allowedFileTypes
:
[
'image/jpeg'
,
'image/png'
,
'video/mp4'
],
onUploadSuccess
:
(
fileUrl
)
=>
{
console
.
log
(
'File uploaded:'
,
fileUrl
);
},
onUploadError
:
(
error
)
=>
{
console
.
error
(
'Upload error:'
,
error
);
}
})
],
content
:
value
,
onUpdate
:
({
editor
})
=>
onChange
(
editor
.
getHTML
()),
...
...
src/extensions/DragAndDrop.js
View file @
c25a5de8
import
{
Extension
}
from
'@tiptap/core'
;
import
{
Plugin
,
PluginKey
}
from
'prosemirror-state'
;
import
{
EditorView
}
from
'prosemirror-view'
;
import
axios
from
'axios'
;
export
const
DragAndDrop
=
Extension
.
create
({
...
...
@@ -9,132 +8,153 @@ export const DragAndDrop = Extension.create({
addOptions
()
{
return
{
uploadUrl
:
''
,
// URL для загрузки файлов
uploadHandler
:
null
,
// Альтернативный обработчик загрузки
types
:
[
'image'
],
// Поддерживаемые типы файлов
uploadHandler
:
null
,
// Кастомный обработчик загрузки
allowedFileTypes
:
[
// Разрешенные MIME-типы
'image/jpeg'
,
'image/png'
,
'image/gif'
,
'image/webp'
,
'video/mp4'
,
'video/webm'
,
'audio/mpeg'
],
headers
:
{},
// Дополнительные заголовки
onUploadError
:
(
error
)
=>
console
.
error
(
'Upload failed:'
,
error
),
onUploadSuccess
:
()
=>
{}
// Колбек при успешной загрузке
};
},
addProseMirrorPlugins
()
{
if
(
!
this
.
options
.
uploadUrl
&&
!
this
.
options
.
uploadHandler
)
{
return
[];
}
const
extension
=
this
;
// Проверяем, является ли файл реальным (не из Word)
const
isRealFile
=
(
file
)
=>
{
if
(
!
file
||
!
file
.
type
)
return
false
;
// Игнорируем специфичные для Word типы
const
wordTypes
=
[
'application/x-mso'
,
'ms-office'
,
'wordprocessingml'
,
'application/rtf'
,
'text/rtf'
,
'text/html'
];
const
isRealFile
=
(
item
)
=>
{
// Игнорируем текстовые форматы и специфичные для Word
if
(
item
.
type
.
startsWith
(
'text/'
)
||
item
.
type
.
startsWith
(
'application/x-mso'
)
||
item
.
type
===
'text/html'
||
item
.
type
===
'text/rtf'
)
{
if
(
wordTypes
.
some
(
type
=>
file
.
type
.
includes
(
type
)))
{
return
false
;
}
// Разрешенные файловые типы
return
[
'image/'
,
'video/'
,
'audio/'
,
'application/octet-stream'
,
'application/pdf'
,
'application/zip'
].
some
(
type
=>
item
.
type
.
startsWith
(
type
));
// Проверяем разрешенные типы
return
extension
.
options
.
allowedFileTypes
.
includes
(
file
.
type
);
};
const
uploadFile
=
async
(
file
)
=>
{
if
(
this
.
options
.
uploadHandler
)
{
return
await
this
.
options
.
uploadHandler
(
file
);
}
// Определяем тип ноды для вставки
const
getNodeType
=
(
mimeType
)
=>
{
if
(
mimeType
.
startsWith
(
'image/'
))
return
'image'
;
if
(
mimeType
.
startsWith
(
'video/'
))
return
'video'
;
if
(
mimeType
.
startsWith
(
'audio/'
))
return
'audio'
;
return
null
;
};
// Обработчик загрузки файла
const
handleFileUpload
=
async
(
file
,
view
,
position
)
=>
{
try
{
let
fileUrl
;
if
(
extension
.
options
.
uploadHandler
)
{
fileUrl
=
await
extension
.
options
.
uploadHandler
(
file
);
}
else
{
const
formData
=
new
FormData
();
formData
.
append
(
'file'
,
file
);
const
headers
=
{
'Content-Type'
:
'multipart/form-data'
,
...
this
.
options
.
headers
};
const
response
=
await
axios
.
post
(
this
.
options
.
uploadUrl
,
extension
.
options
.
uploadUrl
,
formData
,
{
headers
}
{
headers
:
{
'Content-Type'
:
'multipart/form-data'
,
...
extension
.
options
.
headers
,
},
}
);
if
(
!
response
.
data
)
throw
new
Error
(
'Upload failed'
);
return
response
.
data
.
file_path
;
};
const
handleUpload
=
async
(
file
,
view
,
pos
)
=>
{
try
{
const
filePath
=
await
uploadFile
(
file
);
if
(
!
filePath
)
return
;
const
{
state
}
=
view
;
const
{
tr
}
=
state
;
let
node
;
if
(
file
.
type
.
startsWith
(
'image/'
)
&&
this
.
options
.
types
.
includes
(
'image'
))
{
node
=
state
.
schema
.
nodes
.
image
?.
create
({
src
:
filePath
});
}
else
if
(
file
.
type
.
startsWith
(
'video/'
)
&&
this
.
options
.
types
.
includes
(
'video'
))
{
node
=
state
.
schema
.
nodes
.
video
?.
create
({
src
:
filePath
});
}
else
if
(
file
.
type
.
startsWith
(
'audio/'
)
&&
this
.
options
.
types
.
includes
(
'audio'
))
{
node
=
state
.
schema
.
nodes
.
audio
?.
create
({
src
:
filePath
});
if
(
!
response
.
data
?.
file_path
)
{
throw
new
Error
(
'Invalid server response'
);
}
if
(
node
)
{
view
.
dispatch
(
tr
.
insert
(
pos
,
node
));
fileUrl
=
response
.
data
.
file_path
;
}
if
(
!
fileUrl
)
return
;
const
{
state
,
dispatch
}
=
view
;
const
type
=
getNodeType
(
file
.
type
);
if
(
!
type
)
return
;
const
node
=
state
.
schema
.
nodes
[
type
].
create
({
src
:
fileUrl
});
dispatch
(
state
.
tr
.
insert
(
position
,
node
));
extension
.
options
.
onUploadSuccess
(
fileUrl
);
}
catch
(
error
)
{
console
.
error
(
'Upload error:'
,
error
);
extension
.
options
.
onUploadError
(
error
);
}
};
return
[
new
Plugin
({
key
:
new
PluginKey
(
'dragAndDrop'
),
props
:
{
handleDOMEvents
:
{
drop
(
view
,
event
)
{
const
files
=
event
.
dataTransfer
?.
files
;
if
(
!
files
||
files
.
length
===
0
)
return
false
;
// Обработчик вставки (paste)
const
handlePaste
=
(
view
,
event
)
=>
{
const
items
=
Array
.
from
(
event
.
clipboardData
?.
items
||
[]);
const
htmlData
=
event
.
clipboardData
.
getData
(
'text/html'
);
const
coordinates
=
view
.
posAtCoords
({
left
:
event
.
clientX
,
top
:
event
.
clientY
,
});
if
(
!
coordinates
)
return
false
;
// Если есть HTML и это контент из Word - пропускаем
if
(
htmlData
.
includes
(
'urn:schemas-microsoft-com'
))
{
return
false
;
}
// Фильтруем только реальные файлы
const
files
=
items
.
filter
(
item
=>
item
.
kind
===
'file'
)
.
map
(
item
=>
item
.
getAsFile
())
.
filter
(
file
=>
file
&&
isRealFile
(
file
));
if
(
files
.
length
===
0
)
return
false
;
event
.
preventDefault
();
const
pos
=
view
.
state
.
selection
.
from
;
Array
.
from
(
files
).
forEach
(
file
=>
{
if
(
isRealFile
({
kind
:
'file'
,
type
:
file
.
type
}))
{
handleUpload
(
file
,
view
,
coordinates
.
pos
);
}
files
.
forEach
(
file
=>
{
handleFileUpload
(
file
,
view
,
pos
);
});
return
true
;
},
},
handlePaste
(
view
,
event
)
{
const
items
=
event
.
clipboardData
?.
items
;
if
(
!
items
)
return
false
;
};
// Проверяем наличие реальных файлов
const
files
=
Array
.
from
(
items
)
.
filter
(
item
=>
item
.
kind
===
'file'
&&
isRealFile
(
item
))
.
map
(
item
=>
item
.
getAsFile
())
.
filter
(
Boolean
);
// Обработчик перетаскивания (drop)
const
handleDrop
=
(
view
,
event
)
=>
{
const
files
=
Array
.
from
(
event
.
dataTransfer
?.
files
||
[])
.
filter
(
file
=>
isRealFile
(
file
));
if
(
files
.
length
===
0
)
return
false
;
event
.
preventDefault
();
const
pos
=
view
.
posAtCoords
({
left
:
event
.
clientX
,
top
:
event
.
clientY
,
})?.
pos
;
const
{
state
}
=
view
;
const
pos
=
state
.
selection
.
$from
.
pos
;
if
(
!
pos
)
return
false
;
files
.
forEach
(
file
=>
{
hand
leUpload
(
file
,
view
,
pos
);
handleFi
leUpload
(
file
,
view
,
pos
);
});
return
true
;
},
};
return
[
new
Plugin
({
key
:
new
PluginKey
(
'dragAndDrop'
),
props
:
{
handlePaste
,
handleDrop
,
},
}),
];
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment