Commit 2041da4b by Яков

update

parent f31dfa97
...@@ -7,7 +7,7 @@ module.exports = function(app) { ...@@ -7,7 +7,7 @@ module.exports = function(app) {
target: 'https://cdn.atmaguru.online', target: 'https://cdn.atmaguru.online',
changeOrigin: true, changeOrigin: true,
pathRewrite: { pathRewrite: {
'^/upload': 'https://cdn.atmaguru.online/upload/?sid=demo&md5=HNxOMxidAMprpPLfAwdTAg&expires=1751035910', '^/upload': 'https://cdn.atmaguru.online/upload/?sid=demo&md5=G04JoFyFRn5TOviY9R0Nag&expires=1752528156',
}, },
onProxyReq: (proxyReq) => { onProxyReq: (proxyReq) => {
// Добавляем необходимые заголовки // Добавляем необходимые заголовки
......
{ {
"name": "react-ag-qeditor", "name": "react-ag-qeditor",
"version": "1.1.8", "version": "1.1.9",
"description": "WYSIWYG html editor", "description": "WYSIWYG html editor",
"author": "atma", "author": "atma",
"license": "MIT", "license": "MIT",
......
...@@ -35,6 +35,7 @@ import ReactStopwatch from 'react-stopwatch' ...@@ -35,6 +35,7 @@ import ReactStopwatch from 'react-stopwatch'
import Audio from './extensions/Audio' import Audio from './extensions/Audio'
import TableExtension from './extensions/TableExtension' import TableExtension from './extensions/TableExtension'
import ToggleBlock from './extensions/ToggleBlock' import ToggleBlock from './extensions/ToggleBlock'
import InteractiveImage from './extensions/InteractiveImage'
// import Image from '@tiptap/extension-image' // import Image from '@tiptap/extension-image'
// import ImageResize from 'tiptap-extension-resize-image'; // import ImageResize from 'tiptap-extension-resize-image';
...@@ -257,6 +258,12 @@ const QEditor = ({ ...@@ -257,6 +258,12 @@ const QEditor = ({
title: 'Загрузить изображение', title: 'Загрузить изображение',
onClick: () => modalOpener('image', 'Загрузить изображение') onClick: () => modalOpener('image', 'Загрузить изображение')
}, },
interactiveImage: {
title: 'Интерактивное изображение',
onClick: () => {
modalOpener('interactiveImage', 'Интерактивное изображение')
},
},
h2: { h2: {
title: 'Заголовок 2', title: 'Заголовок 2',
onClick: () => editor.chain().focus().toggleHeading({level: 2}).run() onClick: () => editor.chain().focus().toggleHeading({level: 2}).run()
...@@ -551,6 +558,7 @@ const QEditor = ({ ...@@ -551,6 +558,7 @@ const QEditor = ({
Superscript, Superscript,
Subscript, Subscript,
ToggleBlock, ToggleBlock,
InteractiveImage,
DragAndDrop.configure({ DragAndDrop.configure({
uploadUrl: uploadOptions.url, uploadUrl: uploadOptions.url,
allowedFileTypes: [ allowedFileTypes: [
...@@ -616,6 +624,7 @@ const QEditor = ({ ...@@ -616,6 +624,7 @@ const QEditor = ({
if (typeof o.multiple !== 'undefined') { if (typeof o.multiple !== 'undefined') {
multiple = o.multiple multiple = o.multiple
} }
// console.log(o);
return ( return (
<Uploader <Uploader
...@@ -714,6 +723,8 @@ const QEditor = ({ ...@@ -714,6 +723,8 @@ const QEditor = ({
return <Fragment>{getUploader({accept: 'video/mp4,.mp4'})}</Fragment> return <Fragment>{getUploader({accept: 'video/mp4,.mp4'})}</Fragment>
case 'image': case 'image':
return <Fragment>{getUploader({accept: 'image/*'})}</Fragment> return <Fragment>{getUploader({accept: 'image/*'})}</Fragment>
case 'interactiveImage':
return <Fragment>{getUploader({accept: 'image/*', multiple: false})}</Fragment>
case 'file': case 'file':
return ( return (
<Fragment> <Fragment>
...@@ -967,6 +978,11 @@ const QEditor = ({ ...@@ -967,6 +978,11 @@ const QEditor = ({
isDisabled = true isDisabled = true
} }
break break
case 'interactiveImage':
if (uploadOptions.url === null || uploadedPaths.length === 0) {
isDisabled = true
}
break
case 'screencust': case 'screencust':
if (status === 'recording' || isUploading || ! mediaBlobUrl) { if (status === 'recording' || isUploading || ! mediaBlobUrl) {
isDisabled = true isDisabled = true
...@@ -1112,6 +1128,33 @@ const QEditor = ({ ...@@ -1112,6 +1128,33 @@ const QEditor = ({
// editor.chain().focus().setImage({src: file.path}).run(); // editor.chain().focus().setImage({src: file.path}).run();
// }) // })
break break
case 'interactiveImage':
uploadedPaths.map(async (file) => {
const img = new Image()
img.src = file.path
img.onload = () => {
const maxWidth = editor.view.dom.clientWidth - 32 // учёт padding
const realWidth = Math.min(img.naturalWidth, maxWidth)
const realHeight = img.naturalHeight * (realWidth / img.naturalWidth)
editor
.chain()
.focus()
.insertContent({
type: 'interactiveImage',
attrs: {
src: file.path,
width: Math.round(realWidth),
height: Math.round(realHeight),
points: []
},
})
.run()
}
})
break
case 'video': case 'video':
uploadedPaths.map((file, i) => { uploadedPaths.map((file, i) => {
editor.chain().focus().setVideo({ editor.chain().focus().setVideo({
......
...@@ -45,6 +45,7 @@ const toolsInit = { ...@@ -45,6 +45,7 @@ const toolsInit = {
'link', 'link',
'file', 'file',
'image', 'image',
'interactiveImage',
'video', 'video',
'iframe', 'iframe',
'iframe_pptx', 'iframe_pptx',
......
...@@ -107,7 +107,7 @@ export default class Uploader extends React.Component { ...@@ -107,7 +107,7 @@ export default class Uploader extends React.Component {
if(this.action === null){ if(this.action === null){
return <div style={{ textAlign: 'left' }}>{ this.errorMessage }</div> return <div style={{ textAlign: 'left' }}>{ this.errorMessage }</div>
} }
console.log(this.props)
return ( return (
<Fragment> <Fragment>
<div className={'atma-editor-uploader-uitems'}> <div className={'atma-editor-uploader-uitems'}>
......
import { Node, mergeAttributes, ReactNodeViewRenderer } from '@tiptap/react'
import React, { useState } from 'react'
import { NodeViewWrapper } from '@tiptap/react'
import { Button, Modal, Popconfirm, Input } from 'antd'
const InteractiveImageView = ({ node, updateAttributes }) => {
const [modalVisible, setModalVisible] = useState(false)
const [points, setPoints] = useState(node.attrs.points || [])
const addPoint = (e) => {
const rect = e.target.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
const newText = prompt('Введите текст точки:')
if (newText) {
const newPoints = [...points, { x, y, text: newText }]
setPoints(newPoints)
updateAttributes({ points: newPoints })
}
}
const removePoint = (index) => {
const newPoints = points.filter((_, i) => i !== index)
setPoints(newPoints)
updateAttributes({ points: newPoints })
}
return (
<NodeViewWrapper as="div" className="interactive-image-wrapper" contentEditable={false}>
<div style={{ position: 'relative', display: 'inline-block' }}>
<img
src={node.attrs.src}
alt=""
style={{ maxWidth: '100%', display: 'block' }}
/>
<Button
size="small"
type="primary"
onClick={() => setModalVisible(true)}
style={{ position: 'absolute', top: 10, right: 10, zIndex: 10 }}
>
Редактировать
</Button>
{points.map((point, idx) => (
<div
key={idx}
title={point.text}
style={{
position: 'absolute',
top: point.y,
left: point.x,
width: 10,
height: 10,
backgroundColor: 'red',
borderRadius: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 5,
}}
/>
))}
</div>
<Modal
open={modalVisible}
onCancel={() => setModalVisible(false)}
onOk={() => setModalVisible(false)}
title="Редактировать точки"
footer={null}
>
<div style={{ position: 'relative' }}>
<img
src={node.attrs.src}
alt=""
style={{ width: '100%', maxWidth: '100%', display: 'block' }}
onClick={addPoint}
/>
{points.map((point, idx) => (
<Popconfirm
key={idx}
title={
<div>
<Input.TextArea
defaultValue={point.text}
onChange={(e) => {
const updated = [...points]
updated[idx].text = e.target.value
setPoints(updated)
}}
/>
</div>
}
onConfirm={() => updateAttributes({ points })}
onCancel={() => removePoint(idx)}
okText="Применить"
cancelText="Удалить"
>
<div
style={{
position: 'absolute',
top: point.y,
left: point.x,
width: 12,
height: 12,
backgroundColor: 'blue',
borderRadius: '50%',
transform: 'translate(-50%, -50%)',
cursor: 'pointer',
}}
/>
</Popconfirm>
))}
</div>
</Modal>
</NodeViewWrapper>
)
}
export const InteractiveImage = Node.create({
name: 'interactiveImage',
group: 'block',
draggable: true,
selectable: true,
atom: true,
addAttributes() {
return {
src: { default: null },
points: {
default: [], // [{x: 120, y: 90, text: "Hello"}]
parseHTML: el => JSON.parse(el.getAttribute('data-points') || '[]'),
renderHTML: attrs =>
attrs.points.length > 0
? { 'data-points': JSON.stringify(attrs.points) }
: {},
},
}
},
parseHTML() {
return [{ tag: 'interactive-image' }]
},
renderHTML({ HTMLAttributes }) {
return ['interactive-image', mergeAttributes(HTMLAttributes)]
},
addNodeView() {
return ReactNodeViewRenderer(InteractiveImageView)
},
})
export default InteractiveImage
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